feat: Support $$() with toBe() element's matchers - #2149
Conversation
$$() with toBeX() element's matchers
$$() with toBeX() element's matchers$$() with toBe() element's matchers
Greptile SummaryThis PR adds official
Confidence Score: 5/5Safe to merge. The new element-array execution path is well-separated from the existing single-element path, The core No files require special attention. The most complex logic is in
|
| Filename | Overview |
|---|---|
| src/util/executeCommand.ts | Core change: adds multipleElementResultsStrategy and legacyMultipleElementResultsStrategy, with executeCommandWithStrategy routing between them. Logic is correct. |
| src/utils.ts | Refactors executeCommandBe to use executeCommandWithStrategy instead of executeCommand + manual getElement(), enabling element-array support for all toBe* matchers. |
| src/util/formatMessage.ts | Adds array-aware enhanceErrorBe signature with `results: boolean[] |
| src/matchers/element/toBeExisting.ts | Sets this.allowEmptyElements = true so that toExist / toBeExisting / toBePresent behave correctly for empty arrays with .not. |
| src/matchers/element/toHaveText.ts | Adds isNot and strategy: 'LegacyMultipleElements' to preserve existing toHaveText behavior for multi-element / empty-array cases. |
| test/matchers/beMatchers.test.ts | Adds comprehensive multi-element test suite covering ChainablePromiseArray, ElementArray, Element[], edge cases (empty arrays, out-of-bounds, single-element arrays), and .not semantics. |
| test/matchers/element/toBeDisabled.test.ts | Adds multi-element test cases for toBeDisabled, covering both positive and .not scenarios with mocked isEnabled. |
| test/matchers/element/toBeDisplayed.test.ts | Adds comprehensive display-specific multi-element tests including filtered Element[] and edge-case scenarios. |
| test/util/formatMessage.test.ts | Updated to pass new results parameter to enhanceErrorBe; tests now cover per-element diff rendering for array subjects. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["executeCommandBe(received, command, options)"] --> B["waitUntil loop (with isNot, wait, interval)"]
B --> C["executeCommandWithStrategy(unresolvedElements, singleElementCompare, isNot)"]
C -->|"strategy = LegacyMultipleElements (toHaveText)"| D["legacyMultipleElementResultsStrategy"]
C -->|"strategy = NewMultipleElements (toBe* matchers)"| E["multipleElementResultsStrategy"]
D --> D1{"Empty / no element?"}
D1 -->|yes| D2["success: false, actual: undefined"]
D1 -->|"single element"| D3["singleElementCompare(el)"]
D1 -->|"array"| D4["Promise.allSettled() - success: isAllTrue"]
E --> E1{"Empty / no element?"}
E1 -->|yes| E2["success: isNot ? !allowEmptyElements : false"]
E1 -->|"single element"| E3["singleElementCompare(el)"]
E1 -->|"array"| E4["Promise.allSettled() - isNot=false: isAllTrue, isNot=true: !isAllFalse"]
B -->|"breaks or times out"| F["enhanceErrorBe(subject, actual, context, options)"]
F -->|"isElementArrayLike(subject)"| G["Array diff: expected=per-element expected, actual=per-element pass/fail"]
F -->|"single element / other"| H["String diff: expected/actual strings"]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["executeCommandBe(received, command, options)"] --> B["waitUntil loop (with isNot, wait, interval)"]
B --> C["executeCommandWithStrategy(unresolvedElements, singleElementCompare, isNot)"]
C -->|"strategy = LegacyMultipleElements (toHaveText)"| D["legacyMultipleElementResultsStrategy"]
C -->|"strategy = NewMultipleElements (toBe* matchers)"| E["multipleElementResultsStrategy"]
D --> D1{"Empty / no element?"}
D1 -->|yes| D2["success: false, actual: undefined"]
D1 -->|"single element"| D3["singleElementCompare(el)"]
D1 -->|"array"| D4["Promise.allSettled() - success: isAllTrue"]
E --> E1{"Empty / no element?"}
E1 -->|yes| E2["success: isNot ? !allowEmptyElements : false"]
E1 -->|"single element"| E3["singleElementCompare(el)"]
E1 -->|"array"| E4["Promise.allSettled() - isNot=false: isAllTrue, isNot=true: !isAllFalse"]
B -->|"breaks or times out"| F["enhanceErrorBe(subject, actual, context, options)"]
F -->|"isElementArrayLike(subject)"| G["Array diff: expected=per-element expected, actual=per-element pass/fail"]
F -->|"single element / other"| H["String diff: expected/actual strings"]
Reviews (11): Last reviewed commit: "fix ambiguous title & doc" | Re-trigger Greptile
Fixes #1507.
Partially fixes #512.
Partially fixes #1717
Summary
Adds official
$$()(element array/elements) support totoBematchers. Previously, TypeScript signatures allowed arrays (by mistake in this PR), but the implementation didn't support them properly.Example:
Official
$$()SupportThis PR adds official support for
toBemost matchers.$$()support may incidentally enableexpect()to work with multi-remote, this is not intended and may break at any time. Official multi-remote support is tracked here and is not yet available.Types Support
Behavior
The following must pass for all elements to be displayed; otherwise, it fails.
toBematchers, all elements must match the expected boolean (usually true, except for toBeDisabled).isNot
The following must pass when all elements are not displayed; otherwise, it fails.
Edge cases
No elements found
When no elements are found, we always fail, with or without
.not, even if the expected value is an empty array.Error handling
Below are examples of colour failures.
toBeDisplayedmatchers.not,toBeare handled by addingnotin the valuesTODO
Failures Example