I have a form ingesting some strings and a file. The strings are working correctly, but whenever I check to see if the file has been registered correctly, it's always null.
The interface I'm using:
export interface myDetail {
myText: string;
Picture?: File;
}
Form group for image (not working):
<Form
onSubmit={handleSubmit((data) => {
console.log(data);
setDetail({ ...myDetail, ...data });
})}
>
<Form.Group controlId="formFile" className="mb-3">
<Form.Label>Upload Picture</Form.Label>
<Form.Control as="input" type="file" {...register('Picture')} />
</Form.Group>
button:
<AsyncButton type="submit">Save</AsyncButton>
The string variables get assigned fine, but when I check data, the Picture is set to "0, {}"
Thanks for any help
I don't know what the Form.*** component is, so I think it's difficult to answer.
Can it be reproduced with code-sandobox etc.?
If Form.Control is built as a controlled-component,
you should be able to successfully connect to RHF using useController or Controller.
https://react-hook-form.com/api/usecontroller
https://react-hook-form.com/api/usecontroller/controller
JSON.stringify does not parse File objects. Your data is most likely a FileList containing one File object, hence the "0,{}". 0th index in the array, containing an unserializable File object.
If you break at your console.log(data) and examine the data in a debugger, you'll most likely see your File object as expected. The data is there, but not being displayed to the console. Please see this post for more info:
How can I serialize an input File object to JSON?
Alternatively, you can use FileReader to read the File using readAsDataURL().
Related
I am using cypress to test that a textfield displays the entered text correctly.
cy.get('.creation-name').should('not.have.text');
const testName = 'Test name';
cy.get('.creation-name').type(`${testName}`);
TextField is a MUI component and this is a React project.
<div className="create-name">
<TextField
id="Name"
label="Create name"
onChange={(event) => setName(event.target.value)}
/>
</div>
I can see from the video that the text types in fine, but when I try to check that it's there it fails. I have tried multiple different lines to get the test text value, including these:
//got AssertionError for all of these
cy.get('.creation-name').invoke('val').should('equal', 'Test name');//expected '' to equal Test name
cy.contains(`${testName}`).should('have.text', `${testName}`);//expected '' to equal Test name
cy.get('[id="Name"]').should('have.text', `${testName}`);//expected '' to equal Test name
cy.get('.creation-name').invoke('text').should('equal', 'Test');//expected Create name to equal Test name
cy.get('.creation-name').should('have.text', `${testName}`);//expected Create name to equal Test name
Seems I am not getting anything or I am targeting the label. This is my first time using cypress so I hope I didn't miss anything obvious.
Try taking the value from the child <input>.
cy.get('.creation-name') // or .create-name, maybe a typo in the question
.find('input')
.invoke('val')
.should('equal', 'Test name')
The contains() and text related commands won't work because it's an input, which hold the value internally.
Text commands only work for element that have the text between tags like <div>Test text</div>.
Have you tried this ?
cy.get('input[id="Name"]').invoke('val').then(value => assert.equal(value, testName));
I have html DOM like this:
<div class="alert alert-danger">
<button class="close" aria-hidden="true" data-dismiss="alert" type="button">×</button>
Your username or password was incorrect.
</div>
I would like to get Your username or password was incorrect. text.
If I do:
$('.global-alerts div.alert-danger').getText()
Then I get this ×.
Is there a way to get the only text part inside that div element?
I managed to do something like this:
getErrorMessageText() {
return browser.execute(function () {
const selector = '.global-alerts div.alert-danger';
// #ts-ignore
return $(selector).contents().not($(selector).children()).text().trim();
});
}
And it works.
But does anybody have better idea? Or more of like webdriverIO approach here?
Does it work if you use something like this?
var innerHTML = $('.global-alerts div.alert-danger').getHTML(false);
the false argument tells indicates whether or not to include the selector element tag in the output.
Serious solution
I do not quite see any other way but to use execute in order to "grab" that information from the page.
I would however place it in a browser command (either do it in the config "before" hook, or add a service that adds the command in the before hook)
This is what I ended up with considering typescript as main language, ignoring the use of jQuery, and considering that you use the before hook:
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
* #param {Array.<Object>} capabilities list of capabilities details
* #param {Array.<String>} specs List of spec file paths that are to be run
* #param {Object} browser instance of created browser/device session
*/
before: function (_capabilities: any, _specs: any, browser: WebdriverIO.Browser) {
browser.addCommand(
'getNodeText',
async function() {
return this.execute(
(selector: string) =>
Array.from( document.querySelector(selector).childNodes || [])
.filter((n: HTMLElement) => n.nodeType === n.TEXT_NODE)
.map(n => n.textContent)
.reduce(function(f, c) {return f+c;}, '')
.replace('\n', '')
.trim(),
this.selector
);
},
true
);
},
With this approach, typescript might complain about the function that passed to webdriver to get executed, so you can either write it properly, or just move it to a .js file and be done with it.
Just watch for document.querySelector(selector), in theory, it should not be null since the command is executed on an already found by webdriver element.
The way you grab the text there is just await (await $('.alert.alert-danger').getNodeText());
This should return the full string from within the node itself, but not any subchild.
Note: If you end up with an element like: <div id="mytxt">my text style is <strong>strong</strong> and <italic> italic </italic>. - html fan</div> and you do this getNodeText(), you probably end up with the value my text style is and . - html fan.
The "don't get bothered to much" solution
This approach will also sort of check that the "x" button is still there.
await expect($('.global-alerts div.alert-danger')).toHaveText('xYour username or password was incorrect.')
in latest version of WebDriverIO (v8), you can use this selector: aria/YourContent. For example:
With the DOM like this:
<h1>Hello World!</h1>
You can use this selector
console.log(await $('aria/Hello World!').getText()) // outputs: "Hello World!"
Ref: https://webdriver.io/docs/selectors/#fetch-by-content
I'm trying to implement a validation in which i want to disable the button if a specific value entered by user matches the value returned from a service, below is my code:
In the component, i call the service which returns the usernames like below, here is the console log for (UserNames):
0:{Name: "John", userId: "23432"}
1:{Name: "Smith", userId: "12323"}
2:{Name: "Alan", userId: "5223"}
3:{Name: "Jenny", userId: "124"}
in the template, i use NgFor to iterate over the usernames like below
<div *ngFor="let id of UserNames let i = index;">
<input type="radio" name="radio" [checked]="UserNames.userid" (click)="Submit(UserInput)"> <span>Submit</span>
</div>
What i want to achieve is if i enter 23432 the button should disabled because the service already returns userId with this value, unless a new user id entered the button should be enabled.
So the general case of disabling a submit button in the way you're describing would look like:
<button type="submit" [disabled]="someValidationFn()" ...>
and someValidationFn() would, according to the use case you described, contain something like
return UserNames.find(name => { return name.userId === userInput;}));
where userInput is a separate property in the component bound to some user-entered value, presumably via an open text input like
<input name="userInput" [(ngModel)]="userInput" type="text" placeholder="Enter some user id">
But, from the markup snippet you pasted*, I'm not clear that you have that "text" input separate from the radio button group. If the radio button group is meant to have submit actions attached to its individual buttons (it shouldn't), then you're actually guaranteed that the user selection will contain a userId which exists in your UserNames array: the only inputs you're offering are based on the data which came from your service in the first place.
Based on the use case you're describing, I'm not sure why you'd have the radio button group. It sounds like you would just want that text input field with a validation method to make sure that user input does not already exist in the UserNames.
Because I wrote a bunch of abstract snippets there, I thought it might be helpful to show some basic html and js where I put it all together:
// html
<form submit="someSubmitAction()">
<input name="userInput" [(ngModel)]="userInput" type="text" placeholder="Enter some user id">
<button type="submit" [disabled]="someValidationFn()">Submit</button>
</form>
// js
/* don't forget your #Component annotation and class declaration -- I'm assuming these exist and are correct. The class definition encloses all the following logic. */
public userInput: string;
public UserNames: any[];
/* then some service method which grabs the existing UserNames on component's construction or initialization and stores them in UserNames */
public someValidationFn() {
return UserNames.find(name => { return name.userId === userInput;}));
}
public someSubmitAction() {
/* presumably some other service method which POSTs the data */
}
*speaking of the snippet you pasted, there are a couple of errors there:
*ngFor="let id of UserNames <-- you won't get an id by referencing into the UserNames array here; you'll get a member of the UserNames array in each iteration -- i.e., you'd get {Name: "John", userId: "23432"}, then {Name: "Smith", userId: "12323"}, and so on. That's not necessarily an error, but I'm assuming that, b/c you used id as your variable name, you were expecting just the userId field. Otherwise you'd have to use {{id.userId}} in each iteration to access the actual id.
And bob.mazzo mentions another issue with the use of the [checked] attribute
I will reduce the issue to a simple example. I have a table component rendering a table with queried data. I also have a small form that allows users to input data into the table. The issue i am having is that the default value of the input fields are empty strings.
Sample input field:
<td><input type="text" placeholder="Nombre" name="name" onChange={this.handleChange} value={this.state.name}/></td>
Assume that the state is holding an empty string as default value for the input field
this.state={name:' '}
Assume also that i want to send in my form an empty string to the database. I am doing the mutation the apollo way, with graphql:
const ADD_CONTACTS = gql`
mutation createContact(
$name: String,
){
createContact(
name: $name
){
id
name
}
}`
<Mutation mutation={ADD_CONTACTS}>
{createContact=>(
<form onSubmit={async e=> {
e.preventDefault();
let name = this.state.name;
await createContact({variables : {
"name":name
} })
}}
>
<table>
... table markup with input fields
</form>
)}
</Mutation>
Ok, that's the general context. In actuality the form contains 6 fields. But the issue is that when I send an empty string to the query inside the variable, the moment it sends the query to the server it changes the empty string to a null value so that i end up with this error:
GraphQL error: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
I have tested the server using graphiql and the requests themselves can accept empty strings, but it seems to be that the mutation component or apollo, somewhere, is converting my empty string that im passing to the graphql variables into null.
I have managed to make a workaround for this scenario, but it's definitely not the right way to deal with this. If I could somehow get apollo to actually send empty strings instead of null it would solve the issue.
thanks to anyone that has any idea why this i
Try removing async and await.
I am using Angular ng-file-upload (https://github.com/danialfarid/ng-file-upload) on the frontend to manage the file upload process.
Unfortunately, form contains a complex object with multiple files. Using the MultipartFormData (https://www.playframework.com/documentation/2.5.x/ScalaBodyParsers) on the server side I have successfully decomposed the uploaded content and can read it from the request.body.
Now, to my surprise, I do not have a simple Json Objects but rather a strangely formed datatype, described on the ng-file-upload website as:
(...) server implementations expecting nested data object keys in .key or [key] format.
Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file
data: {rec: {name: 'N', pic: file}, objectKey: '.k'} sent as: rec.name -> N, rec.pic -> file
So far I have managed to bring all the data to a common MultipartFormData.Part type, using the DataPart and FilePart like this:
val opts = body.dataParts.map {
case (key, values) => DataPart(key, values.head)
}
val parts = opts ++ body.files
So I am now left with a quite unfortunate Iterable[Part]:
0 = {MultipartFormData$DataPart#86271} "DataPart(arabic[active],false)"
1 = {MultipartFormData$DataPart#86273} "DataPart(english[active],true)"
2 = {MultipartFormData$DataPart#86277} "DataPart(english[url],2132132132)"
...
7 = {MultipartFormData$FilePart#76473} "FilePart(english[image],fb_icon_325x325.png,Some(image/png),TemporaryFile(/tmp/playtemp5909927824995768544/multipartBody8348573128070542611asTemporaryFile))"
Each object name contains the key of it's Json structure and its according value. Now instead of key[level1][level2] I would like to parse it to objects, in my case:
case class PcBanner(english: PcBanners, arabic: PcBanners, kurdish: PcBanners)
case class PcBanners(active: Boolean, url: Option[String], image: Option[String])`
I hope you got the idea.
The question
I know I could try to parse the name strings trying to fit it to objects, but I believe I made a mistake someway in the middle.
Is there a way to parse this structure into the objects, using field names as a reference? Any build in Play functions or alike?
Thanks for help!
As I stated in the title my case was to send images. As you would expect, I am also presenting a preview and the files currently saved in the database.
Considering all pros and cons I have decided to send all the data in JSON format, both ways. Meaning that the images are encoded and sent along in JSON structure.
Despite the fact that above solution looks very convenient it actually creates new problems during the implementation.
You will quickly exceed the server's POST request size limit. For Play server the default 100kB is possible to be extended, but...
I have soon run into some data malformations as the image saved as huge String of bytes probably had some sending/parsing errors.
Not going deeper into this faulty solution I have used the #danial advice:
No have the file sent separately like this
{file: file, otherData: JSON.stringify(myData)}
My solution
If anyone would like to use similar approach to mine I present my answer.
On the front-end side I have decided used ng-file-upload library. Binding it to HTML component with ngf-select with ngf-drop which enables the component:
<div ngf-drop ngf-select
ng-model="image"
ngf-accept="'image/*'"
ngf-resize="{width: {{width}}, height: {{height}}, quality: 1.0, restoreExif: false}">
<img ng-show="!!image && !!image.$ngfName" ngf-src="image">
<img ng-show="(!image || !image.$ngfName)" ng-src="{{ imageUrl }}">
</div>
Inside the upload tag I put the image preview. This works flawlessly. If the image is not selected I use the image saved in the db.
The data and images do not share the model anymore. The upload function looks as follow:
return Upload.upload({
url: url,
data: {file: images, data: angular.toJson(data)}
}).then(function (resp) {
console.log(resp);
}, function (error) {
console.log(error);
});
Putting together all the above gave me the output data object:
{
"english":{
"active":true,
"url":"http://google.com"
},
"arabic":{
"active":true,
"url":"http://google.com"
},
"kurdish":{
"active":true,
"url":"http://google.com"
}
}
On the server side the JSON matches the prepared case class and is parsed with build-in Jackson parser, allowing for easy object manipulation. The image has to be manually selected:
val json = r.body.dataParts("data")
val jsValue = Json.parse(json.head)
val result = jsValue.validate(LocalizedBanner.dataModelFormat) // parse JSON
Extracting the files from body can be done with build in function .file:
val key = s"file[${lang.name}][${imageType.name}]"
body.file(key).map(mp => (mp.ref.file, imageType))
Enjoy!