I'm not able to enter text into a specific input box as determined by a data attribute. There could be dozens of inputs with the same class, so I'd prefer not to add dusk='xxxx' all over the page.
For route and server-side efficiency, the AJAX function pulls a type from the array of inputs and routes to a function that branches the action.
Blade code:
{!! Form::text('question[]', null,
['class'=>'form-control actionChange', "data-id"=>$question->id, "data-type"=>'question']) !!}
The page starts with other questions with a different type, so I have tried using nth-child(x) to grab the selector within the modal, but no success. I've tried using $browser->script() as well.
Reading several similar questions, such as this one, it appears that a loop within the modal is likely the best way to go. This method correctly assigns the selector to the loop variable, $input. It correctly clear()s data, and I've tested similar code with click() and it successfully works. However, it will not enter data in the input. type() and keys() don't appear to work with the RemoteWebElement, so I believe my only choice to enter data is sendKeys().
Dusk test code:
$browser->assertPathIs('/notice')
->whenAvailable('.modal', function($modal) use($browser) {
$modal->assertSee('Survey for:')
->waitFor('#heading')
// WORKS fine
->keys('#heading', 'Edited Heading for Survey', '{enter}')
->waitFor('.actionSurvey');
// Edit a question -- NOT WORKING
foreach ($browser->elements('.actionChange') as $input) {
$dataType = $input->getAttribute('data-type');
if($dataType === 'question') {
$input->clear() // WORKS Fine
->sendKeys('Edited_Question') // NOT successful
break;
I've tried with and without the clear() method, as well as various selector choices both within and out of the modal loop. Same for script() Also, tried using the $modal variable to get the elements, but this was just a guess as I'm a bit out of my depth of understanding at this point.
I probably fubar'd something basic, but I don't understand why one method works, and another doesn't on the same handle.
Related
After years of testing one global DOM for end-to-end testing, I'm finding it very difficult, if not impossible, to test web components that use slots. Before I explain the problem, I want to say that I cannot change the generated markup to improve things as they are.
<wc-1 attributes-etc="">
<wc-2 attributes-etc="">
<slot>
<wc-3 attributes-etc="">
<slot>
...eventually get to an input...
<input type="text" name="firstName" />
There are a buttload of nested web components from some kind of form builder, and there are also plenty of slots used. The web components have attributes but the slots never do, so I use the web component name for querying.
document.querSelector('wc-1')
.shadowRoot.querySelector('wc-2')
.shadowRoot.querySelector('slot')
// Yields <slot>...</slot>
All fine to this point and Cypress has a .shadow() command I used, but I'm testing with just devtools here to see all the properties the slot has.
document.querSelector('wc-1')
.shadowRoot.querySelector('wc-2')
.shadowRoot.querySelector('slot')
.shadowRoot
// Yields "null".
// I don't know how to get to the .lightDOM? of wc-2?
Any property I try ends up being null or having 0 elements in the returned value. Using other front-end tools and the global DOM, I can always cy.get('div[data-testid="the-nested-element-i-want"]').type('important words') in one command.
So my main question is: How do people test these things once web components start piling up? Or don't do this and just test the web components in isolation/unit tests since it's so hard to query the nested shadow DOMs?
The main goal is to eventually get to a form input to cy.get('input[name"firstName"]').type('John'). Can someone give me the chained docuement.querySelector() command to get to <wc-3> in my example?
The answer involves assignedNodes(): https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes
The assignedNodes() property of the HTMLSlotElement interface returns a sequence of the nodes assigned to this slot...
It made no difference for me to use that vs. assignedElements(). So, all you have to do is use that method once you've queried down to the slot you need. For my example, the answer is:
const wc-3 = document.querySelector('wc-1').shadowRoot
.querySelector('wc-2').shadowRoot
.querySelector('slot').assignedNodes()
.map((el) => el.shadowRoot)[0]
And then you can keep going down the chain...I know I only have one un-named slot, so that's why I grab it from the returned .map().
Props to this Q&A for pointing me on the right direction: Web components: How to work with children?
There will be no DOM content in your <slot>, as there is no DOM content moved to slots.
lightDOM content is reflected in slots, but remains invisible! in lightDOM.
(that is why you also style slotted content in lightDOM)
From the docs:
𝘾𝙤𝙣𝙘𝙚𝙥𝙩𝙪𝙖𝙡𝙡𝙮, 𝙙𝙞𝙨𝙩𝙧𝙞𝙗𝙪𝙩𝙚𝙙 𝙣𝙤𝙙𝙚𝙨 𝙘𝙖𝙣 𝙨𝙚𝙚𝙢 𝙖 𝙗𝙞𝙩 𝙗𝙞𝙯𝙖𝙧𝙧𝙚.
𝙎𝙡𝙤𝙩𝙨 𝙙𝙤𝙣'𝙩 𝙥𝙝𝙮𝙨𝙞𝙘𝙖𝙡𝙡𝙮 𝙢𝙤𝙫𝙚 𝘿𝙊𝙈; 𝙩𝙝𝙚𝙮 𝙧𝙚𝙣𝙙𝙚𝙧 𝙞𝙩 𝙖𝙩 𝙖𝙣𝙤𝙩𝙝𝙚𝙧 𝙡𝙤𝙘𝙖𝙩𝙞𝙤𝙣 𝙞𝙣𝙨𝙞𝙙𝙚 𝙩𝙝𝙚 𝙨𝙝𝙖𝙙𝙤𝙬 𝘿𝙊𝙈.
So to test if something is "in" a slot
you need to check for slot=? attributes on lightDOM elements
and double check if that <slot name=? > actually exists in shadowDOM
Or vice versa
Or hook into the slotchange Event, but that is not Testing
pseudo code:
for the vice-versa approach; can contain errors.. its pseudo code..
function processDOMnode( node ){
if (node.shadowRoot){
// query shadowDOM
let slotnames = [...node.shadowRoot.querySelectorAll("slot")].map(s=>s.name);
// query lightDOM
slotnames.forEach( name =>{
let content = node.querySelectorAll(`[slot="${name}"]`);
console.log( "slot:" , name , "content:" , content );
});
// maybe do something with slotnames in lightDOM that do NOT exist in shadowDOM
// dive deeper
this.shadowRooot.children.forEach(shadownode => processDOMnode(shadownode));
}
}
I kind of struggle to find the answer to my question, and my test don't prove to be useful. So maybe someone here would have hit the same issue that I'm facing.
I have inputs with the following kind of patterned name projects-0-1, project-0-2, project-1-0 and so on... These are file inputs so people can upload a document/an image.
So basically, I've been trying to get a validation message that would (ideally) be something like that:
$validator->getMessageBag()->add('project-*-*', 'File is empty!');
OR
$validator->getMessageBag()->add('project-*', 'File is empty!');
I tried a couple of things already and nothing seems to work.
The reason I get to add a custom message is that file is simply not validated if it comes empty to the $request object. So I first need to check if the $request->hasFile and in case it doesn't I want to add the error message.
Things to consider:
inputs can be dynamically added to the form, so I don't know the exact number of file inputs I need to validate beforehand.
even if this should not impact the code and validation, it's worth noticing that everything happens through ajax as I embed the form on another website. Therefore I created endpoints etc...
Any hint ?
Right, coming back here in case someone faces that issue too. I found a "hacky" way to get there and it does the trick for me.
As each input file is dynamically added to the DOM, I add an extra hidden input that holds the name of the file input as a value.
Then in my controller I do smth like that:
public function createValuesKeyArray ($preset)
{
$regexPattern = '/^'. $preset .'-[0-9]*$/';
$customPresets = preg_grep($regexPattern, array_keys(Input::all()));
$keys = [];
foreach ($customPresets as $customPreset) {
array_push($keys, $customPreset);
}
return $keys;
}
// This allows me to get all hidden input names in an array in order to get its value from the $request
$hiddenInputs = $this->createValuesKeyArray('hidden-project-name');
Once I get this array, I can do stuff like that and dinamycally add my set of rules for the input files present in the DOM:
foreach($hiddenInputs as $hiddenInput){
$globalRules[$request[$hiddenInput]] = 'required';
}
Not sure if this the right way to get there, but it does the job for me and I don't find that code horrible. I'll stick with it until I find a better way.
LUA novice, experimenting with GUI using iup.GetParam using LUA 5.1.
I have a simple use of iup.GetParam (which works fine with a simple callback function testing for OK & Cancel) and am trying to add some simple data validation for the parameters (e.g. testing a parameter for being alphanumeric), but am unsure of the correct approach.
I've searched the reference manual (and for code examples), but drawn a blank so far.
Using the string validation example, if I want to reject the
character entered by the user and display the old value of the
parameter, do I simply return 0 from the callback function, or, do
I also have to reset the value of the parameter to its previous
value before the return? Or is the right approach something
completely different?
In either case, do I have to refresh / update the GUI display with a
separate iup call, or does GetParam handle that for me?
Whatever combination I try, it doesn't appear to work (the parameter happily displays the non-alphanumerics). Debugging shows the validation test and return working as coded, so the advice I'm seeking is to get confirmation of the right approach. Sharing a simple working example would be great.
simply return 0
No, IUP will do everything for you, in this case
Download the "getparam.wlua" from the examples folder, then add to its callback this:
elseif (param_index == 1) then
return 0
You will notice that the integer value is now read-only.
I'm loading data using jQuery (AJAX), which is then being loaded into a table (so this takes place after page load).
In each table row there is a 'select' link allowing users to select a row from the table. I then need to grab the information in this row and put it into a form further down the page.
$('#selection_table').on('click', '.select_link', function() {
$('#booking_address').text = $(this).closest('.address').text();
$('#booking_rate').text = $(this).closest('.rate').val();
});
As I understand it, the 'closest' function traverses up the DOM tree so since my link is in the last cell of each row, it should get the elements 'address' and 'rate from the previous row (the classes are assigned to the correct cells).
I've tried debugging myself using quick and dirty 'alert($(this).closest(etc...' in many variations, but nothing seems to work.
Do I need to do something differently to target data that was loaded after the original page load? where am I going wrong?
You are making wrong assumptions about .closest() and how .text() works. Please make a habit of studying the documentation when in doubt, it gives clear descriptions and examples on how to use jQuery's features.
.closest() will traverse the parents of the given element, trying to match the selector you have provided it. If your .select_link is not "inside" .address, your code will not work.
Also, .text() is a method, not a property (in the semantical way, because methods are in fact properties in Javascript). x.text = 1; simply overrides the method on this element, which is not a good idea, you want to invoke the method: x.text(1);.
Something along these lines might work:
var t = $(this).closest('tr').find('.address').text();
$('#booking_address').text(t);
If #booking_address is a form element, use .val() on it instead.
If it does not work, please provide the HTML structure you are using (edit your question, use jsFiddle or a similar service) and I will help you. When asking questions like this, it is a good habit anyways to provide the relevant HTML structure.
You can try using parent() and find() functions and locate the data directly, the amount of parent() and find() methods depends on your HTML.
Ex. to get previous row data that would be
$('#selection_table').on('click', '.select_link', function(){
$('#booking_address').text = $(this).parent().parent().prev().find('.address').text();
});
Where parent stands for parent element (tr), then prev() as previous row and find finds the element.
Is there a demo of the code somewhere? Check when are you calling the code. It should be after the 'success' of AJAX call.
I have written a dynamic search page to search custom object records. I use a SOQL query and bind the results to a data table. I need to change the output text on one of the columns, based on the value returned.
Example: If the SOQL returned "Tiger", I need to display "Animal", Bird for eagle , etc....
So I guess my question is whether be can I use a Javascript function in the value attribute of Apex:column? Something like:
<apex:column value="renameObjectType({!mt.objectName__c})">
And the renameObjectType function is something like:
function renameObjectType(val)
{
var inputtextvalue=val.value;
if(inputtextvalue.length>0)
{
if(inputtextvalue=="Tiger")
return "Animal";
}
};
This is not working as I want to it to be...Is this even possible?
Thanks,
Calvin
This calls for the handy wrapper class. JavaScript can get tricky in Visualforce in terms of execution order, since there's so much happening behind the scenes anyway. If you haven't already, View Source on a standard contact detail page to see how much browser-side processing is going on.
The safest, fastest and most predictable path for things like these is Apex, imo.
One of the things to realize is that, every time you merge in a value from your controller (using the {!myApexVariable} syntax), Visualforce is actually calling the getter for that variable.
This means that, whenever you merge a variable in that way, you are already calling a function. In fact, you can "trick" Visualforce into simply displaying the value of a function using the following:
*My_VF_Page.page*
<apex:outputText value="{!MyFormattedValue}" />
*My_VF_Controller.apex*
public String getMyFormattedValue() {
if (someOtherValue== null || someOtherValue== '') {
return "N/A";
}
return someOtherValue;
}
What you will notice when loading the Visualforce page is that the getMyFormattedValue() is run every time the page needs to get information about MyFormattedValue. I have found this trick incredibly useful for outputting the values of functions.