I am unit testing a blazor app. I get a ElementNotFoundException. I think the cause for this is an if statement in the the index.razor page. see code below:
<div class="row">
<div class="col-12">
#if ((challenges != null) && (challenges.Count > 0))
{
<MultiStepComponent Id="MultiStepContainer" Challenges="#challenges">
<div class="row p-3">
<div class="col-6" id="challengeContainer">
#foreach(var c in challenges)
{
<MultiStepNavigation Name="#c.Title">
<h1>#c.Title</h1>
<img class="float-left" src="#c.ImagePath" width="200" />
#foreach(var sentence in c.Description)
{
<p>#sentence</p>
}
</MultiStepNavigation>
}
</div>
<div class="col-6">
<textarea rows="26" cols="120" #bind="input" id="input"></textarea>
<button class="btn" id="runBtn" #onclick="RunAsync">Run</button>
<br />
<textarea rows="10" cols="120" id="output" readonly>#((MarkupString)Output)</textarea>
</div>
</div>
</MultiStepComponent>
}
</div>
</div>
The code behind of this page (index.razor.cs) has the following initialization code:
protected override async Task OnInitializedAsync()
{
jsonRepository = new JSONChallengeRepository();
challenges = await jsonRepository.GetChallengesAsync();
}
The test for this page is here:
[Test]
public async Task Compile_code_Success()
{
_codingChallengeService.Setup(c => c.SendInputToCompilerAsync("50+50")).ReturnsAsync("100");
_testContext.Services.AddScoped(x => _codingChallengeService.Object);
var razorComponent = _testContext.RenderComponent<Index>();
razorComponent.Instance.challenges = GetChallenges();
if ((razorComponent.Instance.challenges != null) && (razorComponent.Instance.challenges.Count > 0))
{
var runBtn = razorComponent.FindAll("button").FirstOrDefault(b => b.OuterHtml.Contains("Run"));
var input = razorComponent.Find("#input");
input.Change("50+50");
runBtn.Click();
var outputArea = razorComponent.Find("#output");
var outputAreaText = outputArea.TextContent;
Assert.AreEqual("100", outputAreaText);
}
Assert.IsNotNull(razorComponent.Instance.challenges);
}
The #input is missing..Why??
Thanks in advance!
I am guessing the problem is that you do not cause the component under test to re-render when you assign razorComponent.Instance.challenges property/field, and if the component does not re-render, then the markup inside #if ((challenges != null) && (challenges.Count > 0)) block in the component is not displayed.
In general, dont mutate properties (parameters) of components through the razorComponent.Instance. If you really have to do so, make sure to trigger a render after.
Instead, pass parameters to the component through the RenderComponent or SetParametersAndRender methods, or through services injected into components. That will cause the component to go through its normal render life-cycle methods.
Related
I want to get a Random Quote from an API I'm using, however it's not displaying the quote when the page loads, only after pressing a button, the function isnt binded to anything but works properly when i click the button for some reason. I tried to execute the method which loads the quote from an endpoint properly, but does not enter in the if(quote != null) only when i click the button.
`
#page "/"
#inject HttpClient Client
<PageTitle>Index</PageTitle>
<body>
<h3 class="title">Breaking bad</h3>
<div class="div1" >
<input type="text" class="accordion-button" placeholder="Personaje" #bind="nombre" />
<input type="button" #onclick="GetCharacter" class="button" />
</div>
#if (aratzfag == false){
#GetRandomQuote();
#if(quote != null){
<p>#quote[0].quote</p>
}
}
<div class="div2" style="display:#display">
#if (character != null)
{
<div class="card-body">
<br>
<Character name=#character[0].name/>
<Character nickname=#character[0].nickname />
#for (int i = 0; character[0].occupation.Length > i; i++){
string a = character[0].occupation[i];
<span>#a</span>
<br/>
}
<br />
#if(character[0].name == "Saul Goodman"){
<img src="https://media.tenor.com/pMhSj9NfCXsAAAAd/saul-goodman-better-call-saul.gif" width="250px" height="250px" />
}else if (character[0].name == "Walter White")
{
<img src="https://c.tenor.com/0VT7jilszwEAAAAC/mr-white-heisenberg.gif" width="250px" height="250px" />
}
else{
<img src="#character[0].img" width="250px" height="250px" />
}
</div>
}else if(character == null){
<div class="card-body">
<h3>El personaje introducido no existe, introduce bien el nombre</h3>
</div>
}
</div>
</body>
#code {
string nombre = "";
Class1[] character;
Class2[] quote;
string display = "none";
bool aratzfag = false;
async Task GetCharacter(){
// var task = await Client.GetFromJsonAsync<Class1>(Endpoints.GetCharacter(nombre));
// var jsonString = await task.Content.ReadAsStringAsync();
// Class1[] result = System.Text.Json.JsonSerializer.Deserialize<Class1[]>(task);
// Rootobject characterData = await Client.GetFromJsonAsync<Rootobject>(Endpoints.GetCharacter(nombre));
// characters = System.Text.Json.JsonSerializer.Deserialize<Class1>(Endpoints.GetCharacter(nombre));
// var a = System.Text.Json.JsonSerializer.Deserialize<List<Class1[]>>(Endpoints.GetCharacter(nombre));
character = await Client.GetFromJsonAsync<Class1[]>(Endpoints.GetCharacter(nombre));
if (character.Length == 0){
character = null;
}
display = "flex";
//aa = System.Text.Json.JsonSerializer.Deserialize<Class1>(characters);
// var personaje = Endpoints.GetCharacter(nombre);
// characters = await Client.GetFromJsonAsync<Rootobject>(Endpoints.GetCharacter(characters1.Property1[0].name));
}
async Task GetRandomQuote()
{
quote = await Client.GetFromJsonAsync<Class2[]>(Endpoints.GetRandomQuote());
}
}
`
I also tried
`
protected override async Task OnAfterRenderAsync(bool firstRender)
{
quote = await Client.GetFromJsonAsync<Class2[]>(Endpoints.GetRandomQuote());
}
`
protected override async Task OnInitializedAsync()
but won't enter in the if statement and show the quote, only when i interact the button. I made sure to debug it and gets the quote from the api but won write it on the page.
Page loaded
After I click the button on the right of the input box
You want the random quote to be shown when opening the page and not change the quote everytime you rerender something, use OnInitializedAsync(). Your first piece of code shows you calling GetRandomQuote (which is async Task) in the UI part without await, this should be called with await in the #code section.
protected override async Task OnInitializedAsync()
{
await GetRandomQuote();
}
I am trying to set the disabled property in a button based on a value i am setting OnInitialized. But the button remains disabled even when EnableRegistration is set to true OnInitialized. How to do toggle the disabled property based on the EnableRegistration?
#page "/course/register"
<h3 class="my-5">Course Registration</h3>
<div class="container">
<div class="alert alert-info">
<p class="fw-bold">People who register for full course will be given priority over individual module registrations.
After registration, look for an email from us to see if you got in and instructions to pay the course fee.
Registrations will open at 6:00 PM on March 20th.
</p>
</div>
<div class=row>
<div class="col-6">
<button onclick="Register" id="fullcourse" disabled="#(EnableRegistration == true ? "false": "true")" class="btn btn-primary">Register for Full Course</button>
</div>
<div class="col-6">
<button onclick="Register" id="fullcourse" disabled="#(EnableRegistration == true ? "false": "true")" class="btn btn-secondary">Register for First Module</button>
</div><br/>
#if (!string.IsNullOrEmpty(Message))
{
<p>#Message</p>
}
</div>
</div>
#code {
public bool EnableRegistration;
public bool Registered = false;
public string Message = "";
protected override void OnInitialized()
{
var registrationDateTime = new DateTime(2022, 3, 15, 6, 33, 0);
if(Registered == false && (DateTime.Compare(DateTime.Now, registrationDateTime) == 1 || DateTime.Compare(DateTime.Now, registrationDateTime) == 0))
{
EnableRegistration = true;
}
}
public void Register()
{
Message = "Registered";
}
}
Thanks.
Replace
disabled="#(EnableRegistration == true ? "false": "true")"
with
disabled="#(!EnableRegistration)"
HTML does not support ="true" but Blazor does. In the end result you need either just disabled or an empty string. The razor compiler arranges that for you.
You need
<button id="fullcourse" disabled="#(!EnableRegistration)" class="btn btn-primary">Register for Full Course</button>
<div>
<div>
<div class="#(Base64Images.Count == 0 ? "block" : "hidden")">
<label for="file-upload">
<span>Upload a file</span>
<InputFile OnChange="HandleChange" id="file-upload" name="file-upload" class="sr-only" />
</label>
</div>
<div class="#(Base64Images.Count > 0 ? "block" : "hidden")">
#foreach(var image in Base64Images)
{
<img src="#image" />
}
</div>
</div>
</div>
#code {
public IReadOnlyList<IBrowserFile> BrowserFiles { get; protected set; } = new List<IBrowserFile>();
private List<string> Base64Images { get; set; } = new List<string>();
private async Task<bool> HandleChange(InputFileChangeEventArgs e)
{
IReadOnlyList<IBrowserFile> fileList;
BrowserFiles = new List<IBrowserFile> { e.File };
await BrowserFilesToBase64Images();
return true;
}
private async Task<bool> BrowserFilesToBase64Images()
{
foreach(var image in BrowserFiles)
{
if(image != null)
{
var format = "image/png";
var buffer = new byte[image.Size];
await image.OpenReadStream().ReadAsync(buffer);
Base64Images.Add($"data:{format};base64,{Convert.ToBase64String(buffer)}");
}
}
return true;
}
}
So I have this code, it's pretty simple. I want to display a preview of what the use uploads, but the preview must only be displayed after the file was selected. Likewise, I want to hide the input (but not remove it from the DOM) when there is an image loaded. But no matter what I do, Blazor won't re-render.
Base64Images.Count
Changes and I have been able to debug it. The conditions should be hit, but the HTML won't change. Is there any way to tell Blazor to re-render?
I know of StateHasChanged(), but not only that one is supposedly called in after every event, but even calling it multiple times doesn't force the re-render.
You'll have to explain what you want to happen. You have Lists, but when you handle the FileInput's OnChange, you're only getting one File (maybe).
If you want multiple files, then you'll have to set your FileInput like this:
<InputFile OnChange="HandleChange" id="file-upload" name="file-upload" class="sr-only" multiple />
And to get the collection of IBrowserFile objects, this:
BrowserFiles = e.GetMultipleFiles(maxAllowedFiles);
Here's my test code based on what you've given us. It works, so we're missing something obvious.
#page "/Images"
<div class="#(Base64Images.Count > 0 ? "block" : "hidden")">
#foreach (var image in Base64Images)
{
<h4>Images goes here</h4>
<img src="#image" />
}
</div>
#if (!_hasImages)
{
<div>
<InputFile OnChange="#OnInputFileChange" multiple />
</div>
}
else
{
<div>
#foreach (var image in Base64Images)
{
<h4>More Images goes here</h4>
<img src="#image" />
}
</div>
}
<button class="btn btn-dark" #onclick="() => Click()"> Click</button>
#code {
List<string> Base64Images = new List<string>();
private bool _hasImages => Base64Images != null && Base64Images.Count > 0;
void Click()
{
Base64Images.Add("Bye");
}
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
await Task.Delay(1000);
Base64Images.Add("Bye");
}
}
I'm more familiar with WebForms (which my company uses), and just started learning MVC. I followed the movies tutorial, and am having a bit of a hard time seeing how I can use the MVC pattern the way I want.
For instance, my company often asks me to make web apps that take some input from the user, looks it up in our database, and gives them back some other piece of data pertaining to the input, all on the same page.
With WebForms I can easily see if the database returned null and display an error label in that case. But with MVC treating data access like an API, I don't see how I can do the same thing without ignoring using the proper 404 code. Let's say I want to look up a movie (I commented out the 404s on purpose for now):
public class MoviesController : Controller
{
private readonly MovieDBContext _context;
public MoviesController(MovieDBContext context)
{
_context = context;
}
// GET: Movies/Info/Title
public async Task<IActionResult> Info(string title)
{
//if (title == null)
//{
//return NotFound();
//}
var movie = await _context.Movies
.FirstOrDefaultAsync(m => m.Title == title);
// if (movie == null)
// {
// return NotFound();
//}
return Json(movie);
}
Then in the view:
<h2>App</h2>
<br />
<div class="form-group">
<p>
Price Lookup App
</p>
<br />
<div class="row">
<div class="col-xs-2">
Enter movie title:
</div>
<div class="col-xs-2">
<input type="text" class="form-control" id="title" />
</div>
<div class="col-xs-2">
<input type="submit" class="btn" onclick="getMovie()" />
</div>
</div>
<br />
<div class="row">
<label id="price"></label>
</div>
</div>
<script>
function getMovie() {
var movie = $("#title").val();
if (movie != "") {
$.getJSON("../Movies/Info?title=" + movie, function (response) {
if (response != null) {
$("#price").html(response.price);
}
else {
$("#price").html("Film not found");
}
});
}
else {
$("#price").html("Film not found");
}
}
</script>
This technically works, but I feel I'm really not supposed to do it this way. I should be giving a 404 code when the movie isn't found, correct? I just don't know how to handle getting a 404 response and displaying a custom "not found" message on the same view like I am above, because the 404 stops execution of the JS. Thanks.
You can use $.ajax (ajax call), or $.get(get api call). There are componenets of success and error. you can handle the responses there
We're developing news website we're confused with some concept of usage. I'd like to ask and know better if possible. We've a homepage which may contain a lot of models at once so we're separating our homepage to partial views and we're planning to feed them with the appropriate models.
In one partial we're enumerating in categories that are not marked as deleted and we've two types of categories. One of them displays the latest post and the other displays 4 posts at once. We've achieved this actually but as i've mentioned we would like to know if there is a better way or if we're doing anything wrong because right now we're keeping the connection to the context open until the partial is rendered.
Here is the code for views
Partial View Code (CategoryRepeater.cshtml)
#using SosyalGundem.WebUI.DatabaseContext;
#{
var categoryList = new List<PostCategories>();
var db = new SosyalGundemDb();
categoryList = db.PostCategories.Include("Posts").Where(x => !x.IsDeleted).ToList();
}
#for (int i = 0; i < categoryList.Count; i++)
{
if (i % 3 == 0 || i == 0)
{
#Html.Raw("<div class=\"row-fluid spacer\">")
}
var category = categoryList[i];
if (category.PostCategoryType == 1)
{
<div class="span4">
<h3 class="title"><span>#category.PostCategoryName</span></h3>
#{
var article = category.Posts.FirstOrDefault();
if (article != null)
{
<article class="post">
<div class="entry clearfix">
<div class="span6">
<a href="#" title="Permalink to Suspen disse auctor dapibus neque pulvinar urna leo" rel="bookmark">
<img width="225" height="136" src="#Url.Content("~/Content/uploadedimages/" + article.Media.ToList()[0].MediaContent )" alt="shutterstock_70184773" />
</a>
</div>
<div class="span6">
<h4 class="smallnewstitle">#article.PostTitle</h4>
<p>#(article.PostSummary.Length > 100 ? article.PostSummary.Substring(0, 100) : article.PostSummary)</p>
<div class="meta">
<span class="date">#article.PostDate.ToString("MMMM dd, yyyy")</span>
</div>
</div>
</div>
</article>
}
}
</div>
}
else
{
<div class="video-box widget span4">
<h3 class="title"><span>#category.PostCategoryName</span></h3>
#{
int cati = 0;
var firstPost = category.Posts.OrderByDescending(x => x.PostDate).FirstOrDefault();
}
#if (firstPost != null)
{
<h4 class="smallnewstitle">#firstPost.PostTitle</h4>
<p>#(firstPost.PostSummary.Length > 100 ? firstPost.PostSummary.Substring(0, 100) : firstPost.PostSummary) </p>
<ul>
#foreach (var item in category.Posts.OrderByDescending(x => x.PostDate))
{
if (cati <= 3)
{
<li>
<a href="#" title="#item.PostTitle" rel="bookmark">
<img width="225" height="136" src="#Url.Content("~/Content/images/dummy/shutterstock_134257640-225x136.jpg")" alt="shutterstock_134257640" />
</a>
</li>
}
else
{
break;
}
cati++;
}
</ul>
}
</div>
}
if (i % 3 == 0 && i != 0)
{
#Html.Raw("</div>")
}
}
#{
db.Dispose();
}
Separate your concerns. You can see this project for start: http://www.codeproject.com/Tips/617361/Partial-View-in-ASP-NET-MVC
Controller
#using SosyalGundem.WebUI.DatabaseContext;
public ActionResult SomeAction()
{
var model = new CategoriesModel
{
NotDeletedCategories = db.PostCategories.Include("Posts").Where(x => !x.IsDeleted).ToList(),
DeletedCategories = db.PostCategories.Include("Posts").Where(x => x.IsDeleted).ToList()
};
return View(model);
}
Model
public class CategoriesModel
{
public List<PostCategories> NotDeletedCategories {get;set;}
public List<PostCategories> DeletedCategories {get;set;}
};
View
#model CategoriesModel
#Html.RenderPartial("DeletedCategories", Model.DeletedCategories)
#Html.RenderPartial("NotDeletedCategories", Model.NotDeletedCategories)
Hi Jinava,
I would suggest bind Model to the View,
Like,
public ActionResult CategoryRepeater()
{
var multiViewModel = new MultiViewModelModel
{
ModelForParialView1= new XYZ(),
ModelForParialView2= new PQR()
};
return View(model);
}
For the View
#model MultiViewModelModel
And then PAss the views with the MultiViewModelModel.ModelForParialView1 and MultiViewModelModel.ModelForParialView2
You can perform all the model operations on the view.
And at the controller level perform all the database operations and release the database connection there itself no need to get that on the view.
Hope this explanation helps you.