Better class name tests in processors

Roadmap Card

Description

Right now it’s a bit complex the way people can test class names in a processor:

const test = node => node.props.className.split(" ").includes("my-class")

const test = node => /my-class/.test(node.props.className)

User Stories

As a package developer
I want to add processors based on class names
so that the processor tests are easy to use and understand

Possible solution

@orballo proposed these two solutions:

1. Add hasClass() to node

const test = node => node.hasClass("my-class");

2. Switch className to an array

const test = node => node.className.inclues("my-class");

I’m more in favor of the first one because it’s backward compatible.

I totally agree.

I think we could redefine this feature to something more general, like adding helpers for processors tests. Another case that I found in some occasions is when you need to get an array of nodes after searching through the tree, which usually requires to implement a recursive function. Maybe something like the following could be helpful:

node.find(node => node.component === "img")

Could you add an example of what you are doing right now?

Here is an example of a gallery from where I’d like to get an array of images to map them to an array of urls later:

const process = node => {
    const images = (function searchImages(node: Node) {
      let result: Node[] = [];

      if (node.type !== "element") return result;

      if (node.component === "img") {
        result.push(node);
      } else if (node.children && node.children.length) {
        node.children.forEach(child => {
          result = result.concat(searchImages(child));
        });
      }

      return result;
    })(node);
   
   node.props.media = images.map(image => image.props.src);
   node.component = Gallery;

   return node;
}

This could be something like:

const process = node => {
    const images = node.find(node => node.type === "element"
      && node.component === "image");
   
   node.props.media = images.map(image => image.props.src);
   node.component = Gallery;

   return node;
}

Ok, I see. What about allowing you to apply the same processor pattern?

const images = [];
node.process({
  test: node.component === "image",
  process: node => { images.push(node); }
})

That sounds good to me.

Ok, I think that would be easier. Anyway, we need a different feature for this. Would you mind opening it @orballo :slight_smile:

As suggested by @orballo in Styling the HTML content with the `css` prop we can also do something like this instead of node.hasClass:

{
 test: node => node.is(".wp-block-button"),
 test: node => node.is("a.wp-block-button"),
 test: node => node.is("#accept"),
 test: node => node.is('input[type="submit"]'),
}

I think it’s a great idea because people are already used to this syntax and it’s faster and cleaner.

I wonder if there is a JavaScript library that can extract that selector query to tag, class, id and so on…

It wasn’t hard to find: https://github.com/fb55/css-what and it’s only 4Kbs :clap:

I think we can integrate this with our processors. It’d be really cool to do things like:

// Select anchor that links to a dailymotion video
test: ({ node }) => node.is("a[href^='https://dailymotion.com/video']")

// Select images that belong to a Gutenberg block
test: ({ node }) => node.is("img < .wp-block-image")

// Select images that don't belong to a Gutenberg block
test: ({ node }) => node.is("img < :not(.wp-block-image)")

// Select a figure that has an image children with left alignment
test: ({ node }) => node.is("figure > image.align-left")

// Select a button block with background
test: ({ node }) => node.is(".wp-block-button.has-background")

I have created a little sandbox to play with this: https://codesandbox.io/s/css-what-quop3
You can check it out to see the type of object it returns for each case.