Skip to content
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

feat(custom normalizer): allow custom control of normalization #172

Merged
merged 6 commits into from
Dec 12, 2018

Conversation

RoystonS
Copy link
Contributor

@RoystonS RoystonS commented Dec 8, 2018

What:

(PR for issue #171)

Adds an optional {normalizer} option to query functions; this
is a transformation function run over candidate match text
after it has had trim or collapseWhitespace run on it,
but before any matching text/function/regexp is tested
against it.

The use case is for tidying up DOM text (which may
contain, for instance, invisible Unicode control characters)
before running matching logic, keeping the matching logic
and normalization logic separate.

Why:

It's otherwise not easy to apply normalization logic to DOM text without having to replace large portions of the matching logic.

How:

Added a new optional normalizer function which applies after trim + collapseWhitespace but before matching logic.

I had to make the same changes in several places in the code as there's a lot of similar option-handling code around, unfortunately. That also meant lots of test cases to ensure all those paths are tested.

Checklist:

  • Documentation
  • Tests
  • Ready to be merged
  • Added myself to contributors table

Adds an optional {normalizer} option to query functions; this
is a transformation function run over candidate match text
after it has had `trim` or `collapseWhitespace` run on it,
but before any matching text/function/regexp is tested
against it.

The use case is for tidying up DOM text (which may
contain, for instance, invisible Unicode control characters)
before running matching logic, keeping the matching logic
and normalization logic separate.
@@ -194,3 +194,115 @@ cases(
},
},
)

// A good use case for a custom normalizer is stripping
// out UCC codes such as LRM before matching
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we expand out acronyms like this for those who might be unfamiliar with Unicode (UCC -> Unicode Control Character)? 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I did expand it out in the user-facing docs, but I'll expand it here too.

alexkrolick
alexkrolick previously approved these changes Dec 8, 2018
Copy link
Member

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I have some thoughts around this.

src/matches.js Outdated Show resolved Hide resolved
This commit moves the implementation of `trim` + `collapseWhitespace`,
making them just another normalizer. It also exposes a
`getDefaultNormalizer()` function which provides the default
normalization and allows for the configuration of it.

Removed `matches` and `fuzzyMatches` from being publicly exposed.

Updated tests, added new documentation for normalizer and
getDefaultNormalizer, and removed documentation for the
previous top-level `trim` and `collapseWhitespace` options.
Copy link
Member

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good with just about everything :)

a string and is expected to return a normalized version of that string.

Note: Specifying a value for `normalizer` _replaces_ the built-in normalization, but
you can call `getDefaultNormalizer` to obtain a built-in normalizer, either
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to not allow people to call this function and instead simply export it:

export const normalizer = getDefaultNormalizer({trim: true, collapseWhitespace: true})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, easy to do, but then somebody wouldn't be able to get hold of, say, a default normalizer that only trims but doesn't collapse whitespace.

If you're happy with that, that's fine, and I'll hide getDefaultNormalizer and take all mention of trim and collapseWhitespace out of the docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then somebody wouldn't be able to get hold of, say, a default normalizer that only trims but doesn't collapse whitespace.

I'm thinking that's a pretty minimal use-case, and if it is, they just need to copy/paste this:

function getDefaultNormalizer({trim = true, collapseWhitespace = true} = {}) {
  return text => {
    let normalizedText = text
    normalizedText = trim ? normalizedText.trim() : normalizedText
    normalizedText = collapseWhitespace
      ? normalizedText.replace(/\s+/g, ' ')
      : normalizedText
    return normalizedText
  }
}

Considering we're getting simplicity in exchange for a tiny bit of flexibility for 1% of use cases I think that's a win :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll also have to remove the example for how to match text without trimming, for instance:
getByText(node, 'text', {normalizer: getDefaultNormalizer({trim: false})})

There'd be no easy way to do that without getDefaultNormalizer: they'd have to implement their own whitespace-collapsing normalizer. I don't have much of an opinion on that, but presumably the existing trim and collapseWhitespace options were added because somebody wanted to be able to turn them off?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense, considering I want to deprecate trim as an option. Ok, let's stick with what you have. I'd like to have @alexkrolick give this a look over too, but I'm 👍

Copy link
Collaborator

@alexkrolick alexkrolick Dec 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of the queries initially had different defaults for trim and collapseWhitespace, and they had different enough effects to keep separate (see #31).

I'm on the fence about exporting the factory function, but I definitely don't want to encourage copy-pasting the default implementation - that would be opting-out of future changes.

I think this is good as-is and leaves room to come up with future more streamlined APIs for other use cases.

@@ -6,7 +6,7 @@ export * from './queries'
export * from './wait'
export * from './wait-for-element'
export * from './wait-for-dom-change'
export * from './matches'
export {getDefaultNormalizer} from './matches'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a breaking change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as I commented in the commit, I removed matches and fuzzyMatches: they looked like they were exported accidentally, and aren't documented. Are they officially supported?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They were used in jest-dom but it looks like that dependency was removed:

testing-library/jest-dom#9

testing-library/jest-dom@4c6b572

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... that means they still need to be exposed for old versions of jest-dom that are bringing in new versions of dom-testing-library?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Actually, looking closer at those links, where were matches and fuzzyMatches used in jest-dom? I can see getNodeText, but at a quick skim I can't see matches and fuzzyMatches. It looks like they had their own implementation of matches. Am I missing something?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was referring to this comment testing-library/jest-dom#9 (comment)

But you're right it doesn't look like it was used directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kentcdodds can you confirm whether this is a breaking change? I'm happy to change the PR so matches and fuzzyMatches aren't changed, if necessary, but it'd be preferable not to continue to expose those internals or to have to keep the same APIs for them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this change.

matcher,
{collapseWhitespace = true, trim = true} = {},
) {
function matches(textToMatch, node, matcher, normalizer) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can add this option without making a breaking change to the matches APIs?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can ignore this if removing the matchers as exports per #172 (comment)

Copy link
Collaborator

@alexkrolick alexkrolick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporary hold - there are breaking changes

@kentcdodds
Copy link
Member

I hope that you don't mind if we merge this one first: #169 😬

alexkrolick
alexkrolick previously approved these changes Dec 11, 2018
@kentcdodds
Copy link
Member

Let's wait for the new getByDisplayValue to get merged, then update this to account for that one as well, then we can get it merged. Thanks for your help and patience @RoystonS!

@RoystonS
Copy link
Contributor Author

RoystonS commented Dec 11, 2018

Sure. Will keep an eye on the getByDisplayValue PR and then give that the same normalizer treatment.

@RoystonS
Copy link
Contributor Author

I'm merging in the getByDisplayValue changes now. I'll push a rebase + squash to keep this PR tidy against a newer master.

@RoystonS
Copy link
Contributor Author

Rather than rebasing at this point I've pushed a merge from master and a small delta to apply normalization to queryByDisplayValue. That seems to keep existing review comments easier to follow, but I can push a squashed commit if you prefer?

It's all there now.

Copy link
Member

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 💯

@kentcdodds kentcdodds merged commit a03f056 into testing-library:master Dec 12, 2018
@kentcdodds
Copy link
Member

🎉 This PR is included in version 3.15.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants