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

Issue clicking buttons that get their size from an inner image #617

Open
Paul-Hebert opened this issue Nov 22, 2022 · 1 comment
Open

Issue clicking buttons that get their size from an inner image #617

Paul-Hebert opened this issue Nov 22, 2022 · 1 comment

Comments

@Paul-Hebert
Copy link
Member

I'm running into issues writing a test for a "Media Selector" component. It looks like this:

image

It's a set of buttons, with each button containing an image.

Here's the component markup:

import { CheckMark } from '../../icons/check-mark';
import { MediaSelectorItem } from './media-selector-item';
import './media-selector.scss';

export function MediaSelector({
  selectedItem,
  items,
  onChange,
}: {
  selectedItem: MediaSelectorItem;
  items: MediaSelectorItem[];
  onChange: (value: MediaSelectorItem) => void;
}) {
  return (
    <div className="api-x_c-media-selector">
      {items.map((item: MediaSelectorItem) => {
        // If a custom aspect ratio has been passed in, apply it
        const imageStyles = item.aspectRatio
          ? { '--api-x_media-aspect-ratio': item.aspectRatio }
          : {};

        return (
          <button
            type="button"
            className="api-x_c-media-selector__button"
            aria-pressed={(item.value === selectedItem.value).toString()}
            data-value={item.value}
            key={item.value}
            onClick={() => onChange(item)}
          >
            <CheckMark svgClass="api-x_c-media-selector__check" />
            <img
              style={imageStyles}
              className="api-x_c-media-selector__image"
              src={item.src}
              alt={item.alt}
            />
          </button>
        );
      })}
    </div>
  );
}

And here's my test:

import { withBrowser, PleasantestUtils } from 'pleasantest';

async function setup(utils: PleasantestUtils) {
  await utils.runJS(`
      import { render } from 'preact';
      import { useState } from 'preact/hooks';
      import { Root } from '../root/root';
      import { MediaSelector } from './media-selector';

      // Preact's render function doesn't clear this properly
      document.body.innerHTML = '';

      function MediaSelectorExample() {
        const items = [
          {
            src: 'https://cloudinary-marketing-res.cloudinary.com/image/upload/h_160/sneakers-wide.jpg',
            alt: 'sneakers',
            value: 'sneakers-wide'
          },
          {
            src: 'https://cloudinary-marketing-res.cloudinary.com/image/upload/h_160/car.jpg',
            alt: 'car',
            value: 'car'
          }
        ];
        const [option, setOption] = useState(items[0]);
        return (
          <Root>
            <MediaSelector onChange={setOption} selectedItem={option} items={items} />
          </Root>
        );
      }

      render((
        <MediaSelectorExample />
      ), document.body);
    `);
}

test(
  'Confirm the buttons are pressed correctly',
  withBrowser(async ({ utils, screen, user }) => {
    await setup(utils);

    const firstItem = await screen.getByRole('button', {
      name: 'sneakers',
    });
    const secondItem = await screen.getByRole('button', {
      name: 'car',
    });

    // We set the first item as selected
    await expect(firstItem).toHaveAttribute('aria-pressed', 'true');
    await expect(secondItem).toHaveAttribute('aria-pressed', 'false');

    // I'm not sure why this is necessary.
    // I'm opening a Pleasantest issue to discuss.
    await new Promise((resolve) => setTimeout(resolve, 500));

    // Clicking the first item should do nothing. It's already selected
    await user.click(firstItem, { force: true, targetSize: false });

    // The first item should still be pressed
    await expect(firstItem).toHaveAttribute('aria-pressed', 'true');
    await expect(secondItem).toHaveAttribute('aria-pressed', 'false');

    // Clicking the second item should select it
    await user.click(secondItem, { force: true, targetSize: false });

    // The user clicked the second item. It should be selected
    await expect(secondItem).toHaveAttribute('aria-pressed', 'true');
    await expect(firstItem).toHaveAttribute('aria-pressed', 'false');
  })
);

This test is passing, but only because I've put a 500ms timeout in the middle of it. If I remove this, then I can't click the buttons.

I ran into a few different issues:

  • First off it said the buttons were too small or not visibly.
    • I figured this was because the image hadn't loaded and the button had no width. I added an aspect ratio to the image CSS but it still didn't work
    • Then I added { force: true, targetSize: false }. At this point it worked sometimes but failed other times with a different error:
  • Then it said the buttons are "either not clickable or not an HTMLElement"
    • At this point I added the timeout and it fixed it.

I'm not sure what's going on. This is a private repo but you should have access. Here's the PR: https://github.com/cloudfour/cld-api-explorer/pull/9

Let me know if you need more info or would like to troubleshoot together.

Thanks!

@Paul-Hebert
Copy link
Member Author

I figured this was because the image hadn't loaded and the button had no width. I added an aspect ratio to the image CSS but it still didn't work

I realized why this wasn't working. The image has no intrinsic height so aspect ratio doesn't work.

What I'm still confused about in this case is why {force: true} still isn't allowing me to click the buttons.

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

No branches or pull requests

1 participant