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

Add .some, .every property chain to matcher that expects an array #7629

Open
4 tasks done
bmthd opened this issue Mar 7, 2025 · 4 comments
Open
4 tasks done

Add .some, .every property chain to matcher that expects an array #7629

bmthd opened this issue Mar 7, 2025 · 4 comments

Comments

@bmthd
Copy link

bmthd commented Mar 7, 2025

Clear and concise description of the problem

Currently, when testing elements returned as an array (e.g., screen.getAllByRole in Testing Library), matchers cannot be used directly on individual elements within the array. Specifically, there is no built-in way to check if at least one element satisfies a matcher without manually iterating and extracting a boolean value.

This creates several issues:

  • Manual boolean extraction: Test authors must manually iterate over the array and convert the matcher result into a boolean value.
  • Redundant code: This leads to unnecessary repetition, making tests more verbose.
  • Custom matchers as a workaround: While it is possible to define custom matchers to handle these cases, they must be redefined for every assertion type, increasing maintenance overhead.

Current workarounds

Using Array.prototype.some and a manual boolean assertion

expect(screen.getAllByRole("alert").some((it) => it.textContent === message)).toBe(true);

This approach is verbose and does not leverage existing matchers.

Defining a custom matcher (for each assertion type)

expect.extend({
  toHaveSomeTextContent(received, expected) {
    const pass = received.some((el) => el.textContent === expected);
    return pass
      ? { pass: true, message: () => `Expected at least one element to not have text content ${expected}` }
      : { pass: false, message: () => `Expected at least one element to have text content ${expected}` };
  }
});

expect(screen.getAllByRole("alert")).toHaveSomeTextContent(message);

This solves the problem, but requires defining a separate matcher for each assertion type, which is not practical for general use.
Also, there is the hassle of checking the implementation if you want to reproduce the matcher of the community.

Suggested solution

Introduce .some and .every as built-in property chains, such as Array.prototype, so that assertions can be verified to satisfy some or all of the array.

Proposed syntax

expect(array).some.toHaveTextContent(message);
expect(array).every.toHaveTextContent(message);

Benefits:
Eliminates manual boolean extraction
Avoids the need to define custom matchers for each assertion type
Improves readability by making the test’s intent clear
Enables direct use of community matchers (e.g., testing-library/jest-dom)
For symmetry, .every could also be introduced to allow assertions to be applied to all elements in an array.

.every is not strictly necessary since the combination of for and Array.prototype.every is already a viable alternative. .every is included in the proposal primarily for consistency with .some.

Alternative

No response

Additional context

The implementation should ensure proper TypeScript support so that .some and .every only appear on arrays.
It should be compatible with expect.extend, allowing custom matchers to work seamlessly.
Performance considerations should be taken into account to avoid unnecessary operations when chaining multiple matchers.
By introducing .some (and optionally .every for symmetry), this feature would eliminate boilerplate, improve test readability, and reduce the need for repetitive custom matchers.

Validations

@hi-ogawa
Copy link
Contributor

hi-ogawa commented Mar 8, 2025

It's probably hard to bring more chai-style x.y.some.z assertion as it's likely to have more edge cases with existing assertions.

I think the alternative is to use asymmetric matchers, which could work like:

expect(myArray).toEqual(expect.arrayContaining([expect.toHaveTextContent('yay')]))

Probably you can create custom matcher like toEqualAny(xxx) to work like toEqual(expect.arrayContaining([xxx]), then this can be simplified to:

expect(myArray).toEqualAny(expect.toHaveTextContent('yay'))

@bmthd
Copy link
Author

bmthd commented Mar 10, 2025

@hi-ogawa
Thanks for the reply!
We were able to verify SOME such verification using the method you provided.
I was unaware that you could pass a static matcher via ExpectStatic, so I was unaware of this alternative method.
I understand the difficulty of adding a Chai Style Assertion, so I am closing this Issue.
Thank you for your response!

@bmthd bmthd closed this as completed Mar 10, 2025
@sheremet-va
Copy link
Member

I actually like the idea of having .some/.every. It would also work nicely with Vitest Browser Mode locators that return multiple elements.

@bmthd
Copy link
Author

bmthd commented Mar 11, 2025

@sheremet-va
I'm glad you like it!
If this proposal could be a puzzle piece for Vitest Browser mode, it might be worth revisiting.
Although I closed it once, I would like to reopen the discussion again because I personally think that such an API would be useful.

@bmthd bmthd reopened this Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants