get by label concat values#681
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 49c6148:
|
Codecov Report
@@ Coverage Diff @@
## master #681 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 24 24
Lines 603 630 +27
Branches 151 163 +12
=========================================
+ Hits 603 630 +27
Continue to review full report at Codecov.
|
kentcdodds
left a comment
There was a problem hiding this comment.
This one is also tricky. I have some feedback and questions/ideas.
| const combs = [[]] | ||
| const matching = [] | ||
| for (const label of labels) { | ||
| const copy = [...combs] // See note below. |
There was a problem hiding this comment.
I'm not sure what note this is referring to. Also, if I'm following this logic correctly, the for loop on the next line will only be run only once (for this iteration of the nested loop) and the value of the prefix will be [] every time. Any reason we need a loop here?
There was a problem hiding this comment.
Correct. Because copy is [[]] and so it's an array with only one element in it. It'll never be....
Wait. I guess that's only the case on the first iteration of the outer loop. Future iterations could have more... Hmmm... I need to take more time to understand what's going on here...
There was a problem hiding this comment.
Yes, I think it's only the first iteration of the outer loop that makes the inner one run only once.
| return matching | ||
| } | ||
| function queryAllLabels(container) { | ||
| return Array.from(container.querySelectorAll('label,input')) |
There was a problem hiding this comment.
It occurs to me that an element can be labeled by anything, not just label and input. So while this may be solving for the input case, it doesn't solve for everything else 🤔
That said, I'm not sure how to handle "everything else." It seems to me that rather than searching for all possible elements that can label anything, we should be searching for everything that's labeled first, determine the contents of their label, and then return the elements whose label matches the query.
There was a problem hiding this comment.
For what it's worth, I think that searching for labelled items and then for their labels is more feasible than querying for all elements that could be labels. Like you said above!
There was a problem hiding this comment.
If you'd like to tack a whack at that, I think it would solve a lot of problems.
There was a problem hiding this comment.
For sure I would like to do it!
| const labelsNotMatchingTextAndNotEmpty = textToMatchByLabels.filter( | ||
| ({node, textToMatch}) => | ||
| Boolean(textToMatch) && | ||
| nodesByLabelMatchingText.findIndex(nodeByLabelByText => | ||
| nodeByLabelByText.isEqualNode(node), | ||
| ) === -1, | ||
| ) |
There was a problem hiding this comment.
| const labelsNotMatchingTextAndNotEmpty = textToMatchByLabels.filter( | |
| ({node, textToMatch}) => | |
| Boolean(textToMatch) && | |
| nodesByLabelMatchingText.findIndex(nodeByLabelByText => | |
| nodeByLabelByText.isEqualNode(node), | |
| ) === -1, | |
| ) | |
| const labelsNotMatchingTextAndNotEmpty = textToMatchByLabels.filter( | |
| ({node, textToMatch}) => | |
| textToMatch && nodesByLabelMatchingText.some(n => n.isEqualNode(node)), | |
| ) |
There was a problem hiding this comment.
I think the right change should be:
const labelsNotMatchingTextAndNotEmpty = textToMatchByLabels.filter(
({node, textToMatch}) =>
textToMatch && !nodesByLabelMatchingText.some(n => n.isEqualNode(node)),
)because I would like to retrieve all the labels in textToMatchByLabels but not empty and not included in nodesByLabelMatchingText
|
Hey @kentcdodds ! Now Right now I am still using the "old" I suppose my job is not finished yet but this is what I achieved. |
kentcdodds
left a comment
There was a problem hiding this comment.
Hey @delca85, thanks for this. This isn't quite doing what I thought (getting all labelable elements first, then finding their labels). I'm going to need more time to review/provide feedback on this. Just commenting here to let you know I haven't forgotten.
| <label> | ||
| All alone but with children | ||
| <textarea>Hello</textarea> | ||
| <select><option value="0">zero</option></select> | ||
| </label>`) |
There was a problem hiding this comment.
This should find textarea. And it does so when using label.control
There was a problem hiding this comment.
Maybe I am not understanding, you are saying that the right test to be done here (at line 374) was that the labeled element is textarea instead of that no input element labeled was found?
getByLabelText(/alone/) would have found the textarea element, I added the selector to make it return null.
Please tell me if I am missing something.
|
I apologize it's taken me so long to give feedback here. I've been doing more family stuff than open source stuff recently. I was thinking about a much bigger reworking of the logic for this function. Right now, here's what we try to do:
The #545 should be found via the What I was thinking is we could do this instead:
I think this would allow us to more easily solve #545 and would result in fewer bugs. I expect that this would be a non-breaking change. Does that make sense? |
Don't be sorry at all, family stuff is always first class stuff!
Totally agree with you, specially regarding the additional complex logic required in order to solve #545 .
It makes a lot of sense and it is what I have tried to do in the code I pushed here :
In comparison to what you wrote, I check the value of the elements I found with a label immediately after I found them, because I thought it is more performant (I save a loop doing this instead of checking the value of all the labeled elements after I found all of them). Like I told you here I am still using
I hope I explained what I did in a clear way, if I am still miss something please tell me. |
kentcdodds
left a comment
There was a problem hiding this comment.
Ah, that makes sense. Sorry for my confusion! I had an idea. What do you think?
| container.querySelector(`[id="${label.getAttribute('for')}"]`), | ||
|
|
||
| const matchingLabelledElements = Array.from(container.querySelectorAll('*')) | ||
| .filter(element => element.hasAttribute('aria-labelledby')) |
There was a problem hiding this comment.
I think we might be able to clean up some stuff (and reduce the number of loops) by changing this filter to include elements with the .label property:
| .filter(element => element.hasAttribute('aria-labelledby')) | |
| .filter(element => element.label || element.hasAttribute('aria-labelledby')) |
This would require the reducer function handle the .label elements a little differently, but I think it would allow us to remove some other code as well (specifically all the code for matchingElementsByLabels).
There was a problem hiding this comment.
I am going to try what you suggested as soon as possible.
There was a problem hiding this comment.
Hey @kentcdodds !
For sure I am missing something but so far I have not been able to make the change you suggested me.
Trying to include the elements with the .label property in the filter you highlighted it is not working because the element labeled by a label element put around them have the .label property undefined.
I have tried to show it in this sandbox.
Again, I am aware there is something I am not doing in the right way but I am not able to get it.
There was a problem hiding this comment.
Oh, sorry, that's a typo. It should be .labels. This is a NodeList of all the labels. If there are multiple, then it looks like the Chrome Accessibility DevTools combines them:
<div>
<label for="my-input">One</label>
<label for="my-input">Two</label>
<input id="my-input" />
</div>So I think that's probably what we should do as well. Combine the text for all labels in the NodeList, then compare that against the given TextMatch.
Does that make sense?
There was a problem hiding this comment.
Yes, it makes a lot of sense.
I have just pushed some code using .labels.
I am not so sure I have done all the work because I am not obtaining all the labels in the NodeList through a permutation but just join their values (and join values in the NodeList removing an item a time). I don't know if you meant this with combine.
…instead of looking for label elements (testing-library#545)
kentcdodds
left a comment
There was a problem hiding this comment.
This is looking pretty great! Just one question.
| ? labelledElement.getAttribute('aria-labelledby').split(' ') | ||
| : [] | ||
| const labelsValue = labelsId.length | ||
| ? labelsId.map(labelId => { |
There was a problem hiding this comment.
What happens if an element had both a .labels property and the aria-labelledby? I don't know if that's valid or not. Is it?
There was a problem hiding this comment.
aria-labelledby has precedence: https://www.w3.org/TR/accname-1.1/#step2B
2B is for aria-labelledby, 2D for
There was a problem hiding this comment.
In that case, I think this PR is good to go! Anyone have other thoughts?
kentcdodds
left a comment
There was a problem hiding this comment.
This is super work @delca85! Thank you for your patience with me and all my feedback. You did a superb job 👏
|
@all-contributors please add @delca85 for tests and code |
|
I've put up a pull request to add @delca85! 🎉 |
|
Thanks so much for your help! I've added you as a collaborator on the project. Please make sure that you review the |
|
🎉 This PR is included in version 7.21.2 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
|
Thank you so much @kentcdodds ! |
|
Thank you @delca85. This is PR is really awesome! |

What:
inputelements with not empty value are retrieved bygetAllLabelsByTextgetByLabelTextgets labels even when it receives a label composed of a concatenationWhy:
Issue #545 finds out that
inputelements are not recognized as labels and labelling by concatenation is not got bygetByLabelText.How:
queryAllLabelsByTextnow makes a query not only on elements of typelabel but on input elements too. The empty input are excluded.
matcher, the labels that doesn't match are combined through an helper function. If a combination matches with the received text than the labels is returned byqueryAllLabelsByText.Checklist:
queryAllLabelsByTextlook forinputelements tooqueryAllLabelsfunction that retrieves all labels without any regards about text to be matchedcombinationfunction that concatenate all the labels and returns the node that through some combination matches with the received textqueryAllLabelsByTextreturns now the labels matching by itself and the ones matching through concantenationdocs site
I have already opened #607 to fix this behavior but maybe not in the right way. I hope this idea is better than the one I have submitted before.