-
Notifications
You must be signed in to change notification settings - Fork 470
get by label concat values #681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is also tricky. I have some feedback and questions/ideas.
src/queries/label-text.js
Outdated
const combs = [[]] | ||
const matching = [] | ||
for (const label of labels) { | ||
const copy = [...combs] // See note below. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure I would like to do it!
src/queries/label-text.js
Outdated
const labelsNotMatchingTextAndNotEmpty = textToMatchByLabels.filter( | ||
({node, textToMatch}) => | ||
Boolean(textToMatch) && | ||
nodesByLabelMatchingText.findIndex(nodeByLabelByText => | ||
nodeByLabelByText.isEqualNode(node), | ||
) === -1, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should find textarea
. And it does so when using label.control
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that makes sense. Sorry for my confusion! I had an idea. What do you think?
src/queries/label-text.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to try what you suggested as soon as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
https://jsbin.com/pudalunaxu/1/edit?html,output
<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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, I think this PR is good to go! Anyone have other thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
input
elements with not empty value are retrieved bygetAllLabelsByText
getByLabelText
gets labels even when it receives a label composed of a concatenationWhy:
Issue #545 finds out that
input
elements are not recognized as labels and labelling by concatenation is not got bygetByLabelText
.How:
queryAllLabelsByText
now 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:
queryAllLabelsByText
look forinput
elements tooqueryAllLabels
function that retrieves all labels without any regards about text to be matchedcombination
function that concatenate all the labels and returns the node that through some combination matches with the received textqueryAllLabelsByText
returns 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.