Jacksonkr
Jacksonkr

Reputation: 32207

regex.test() only works every other time

Regex test() is giving me issues in Firefox and Chrome, yet it works flawlessly in Opera and Safari.

troubled code:

var pattern = /(\s+(?!\$\w+)|(^(?!\$\w+)))/g;
if(pattern.test(String(id).replace(/\s+OR|AND\s+/g, ''))) {
 searchError("You suck.");
 return 1;
}

When you pass in white space, it blocks it every time. When you pass in something like '$a b' then it will work every other time in Firefox/Chrome. WEIRD.

Upvotes: 46

Views: 11016

Answers (3)

shieldgenerator7
shieldgenerator7

Reputation: 1736

for some reason, matchAll() works correctly even though test() doesnt

Upvotes: 0

Willie
Willie

Reputation: 338

Update: 9/27/23 - I am STILL running into this issue.

Consider the following:

  1. You are using Angular reactive forms, and are using some built in features, like out of the box Validators.
  2. You use something like the following.
      form.forEach((formField: any) => {
        let validatorsArray = formField.validators?.reduce((validators: any, validator: any) => {
          switch(validator.type){
            case 'required':
              validators.push(Validators.required);
              break;
            case 'pattern':
              validators.push(Validators.pattern(new RegExp(validator.value)));
              break;

where each validator is an object I've defined on our server like so (for example):

{
"name": "lowercaseandunderscore"
"value": "^[a-z_]*$",
"type": "pattern",
"error": "Column names must only contain lower case letters and underscores"
}

Now, one would assume this would inherently work using reactive forms, but it does not! It falls into the same pit as the post in 2021 below. Don't be fooled!

I came across this today.

Oddly, what works is the following:

      form.forEach((formField: any) => {
        let validatorsArray = formField.validators?.reduce((validators: any, validator: any) => {
          switch(validator.type){
            case 'required':
              validators.push(Validators.required);
              break;
            case 'pattern':
              validators.push(Validators.pattern(new RegExp(/^[a-z_]*$/)));
              break;

This does not fail every other time!

But, if you are creating a Regex from using the class, using a STRING... watch out. You'll have to do something like this.

      form.forEach((formField: any) => {
        let validatorsArray = formField.validators?.reduce((validators: any, validator: any) => {
          switch(validator.type){
            case 'required':
              validators.push(Validators.required);
              break;
            case 'pattern':
              validators.push(this.testValue(validator));
              break;

where your testValue function is the following:

private testValue(validator: any): ValidatorFn {
    return (control: AbstractControl) => {
      let returnVal = null;
      if(control.value){
        //NOTE the use of parenthesis around the RegExp below.
        if((new RegExp(validator.value, 'gm').test(control.value))){
          return null;
        } else {
          return {[validator.name]: validator.error};
        }
      }
      return returnVal;
    }
  }

As I was working through this today, I honestly thought "surely I can't be facing that same exact problem with regexes...". Turns out, I was.

There is something to be said for every time you create a RegExp with a string. Just wrap it in parenthesis. For whatever reason, creating it without a string literal works like it should.

I hope this hammers that point home.

Essentially, regexes seem to misbehave when created from string literals, and seem to be just fine when created NOT from a string.

///End of update

This seems to still be an issue in August of 2021... I just want to share some things I have learned before stumbling upon this question and answer. I was baffled by this problem and had no meaningful way forward - until now.

  1. It doesn't matter whether you use exec() or test() or match(). The regex still doesn't work properly on every other occurrence.

  2. It doesn't matter if you set the regex with

let reg = new RegExp(/<table(\s*[^>]*)>/g);

or with const. Doesn't matter if you set it globally or locally either...

What does work to bypass this problem is wrapping your regex statement in parenthesis in your loop like so:

Object.keys(table).forEach(key => {
    if((new RegExp(/<table(\s*[^>]*)>/g)).test(___your test string___)){
        //Do what you need to do
    }
});

Remove the parenthesis around the Regex, and watch every other one fail....

Thank you so much for the answer @Nick Craver and the comment @prototype!

This exact Regex was what was giving me trouble. It would work for one object, and fail for the subsequent object, and it made no sense. I am only here to say that this is still very relevant in 2021.

Upvotes: 6

Nick Craver
Nick Craver

Reputation: 630379

It's a bug in the RegEx engine, a similar question with the same issue came up here.

From my answer to that question: It's a bug with the way regexes are implemented in ECMAScript 3, there's a great post on the details here.

The basics are a /regex/ with the g modifier doesn't reset correctly, so multiple .test() calls alternate between true and false if everyone should be true, every other calls successfully resets it.

Upvotes: 97

Related Questions