chore(components): upgrade LeafyGreen modal components COMPASS-9642#7594
chore(components): upgrade LeafyGreen modal components COMPASS-9642#7594kraenhansen merged 42 commits intomainfrom
Conversation
7b62353 to
694ac4e
Compare
There was a problem hiding this comment.
Pull request overview
This PR upgrades LeafyGreen modal-related packages to their latest versions. The upgrade introduces breaking changes in the LeafyGreen Modal API, requiring updates to modal usage patterns throughout the codebase. Key changes include updating modal prop names, refactoring modal state detection in tests, and adjusting focus management behavior.
- Updated LeafyGreen modal packages and related dependencies to latest versions
- Refactored modal prop usage from deprecated patterns (e.g.,
contentClassName→className,onButtonClick→buttonProps) - Implemented new modal state detection utilities for E2E tests using dialog element attributes
- Updated autofocus handling to use explicit
autoFocusprops instead ofinitialFocus
Reviewed changes
Copilot reviewed 102 out of 103 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/compass-components/package.json | Updated LeafyGreen dependencies to latest versions |
| packages/compass-components/src/components/modals/* | Refactored modal components to use new API patterns |
| packages/compass-e2e-tests/helpers/commands/* | Added new modal state detection utilities and updated test helpers |
| packages/compass-e2e-tests/tests/* | Updated E2E tests to use new modal detection methods |
| configs/testing-library-compass/src/assertions.ts | Added custom chai assertions for dialog open/closed states |
| configs/mocha-config-compass/register/jsdom-extra-mocks-register.js | Added HTMLDialogElement polyfill for jsdom |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| userEvent.click(startButton); | ||
| await waitFor(() => { | ||
| expect(() => screen.getByTestId('welcome-modal')).to.throw(); | ||
| expect(screen.getByTestId('welcome-modal')).be.closed; |
There was a problem hiding this comment.
Missing 'to' in the chai assertion chain. Should be expect(screen.getByTestId('welcome-modal')).to.be.closed;
| userEvent.click(closeButton); | ||
| await waitFor(() => { | ||
| expect(() => screen.getByTestId('welcome-modal')).to.throw(); | ||
| expect(screen.getByTestId('welcome-modal')).be.closed; |
There was a problem hiding this comment.
Missing 'to' in the chai assertion chain. Should be expect(screen.getByTestId('welcome-modal')).to.be.closed;
| userEvent.click(settingsLink); | ||
| await waitFor(() => { | ||
| expect(() => screen.getByTestId('welcome-modal')).to.throw(); | ||
| expect(screen.getByTestId('welcome-modal')).be.closed; |
There was a problem hiding this comment.
Missing 'to' in the chai assertion chain. Should be expect(screen.getByTestId('welcome-modal')).to.be.closed;
bc02a6d to
4467057
Compare
bac36cb to
8c0d915
Compare
| expect(screen.getByTestId('assistant-confirm-clear-chat-modal')).to | ||
| .exist; | ||
| expect( | ||
| screen.getByTestId('assistant-confirm-clear-chat-modal').firstChild |
There was a problem hiding this comment.
Using .firstChild here because the modal root (dialog element) always exists, regardless of it being open or not.
| declare global { | ||
| // eslint-disable-next-line @typescript-eslint/no-namespace | ||
| export namespace Chai { | ||
| interface Assertion { | ||
| /** Asserts that a dialog is open */ | ||
| get open(): Assertion; | ||
| /** Asserts that a dialog is closed */ | ||
| get closed(): Assertion; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| util.addProperty( | ||
| Assertion.prototype, | ||
| 'open', | ||
| function (this: typeof Assertion) { | ||
| const obj = util.flag(this, 'object'); | ||
| new Assertion(obj).to.be.instanceof(HTMLDialogElement); | ||
| new Assertion(obj as HTMLDialogElement).has.property( | ||
| 'open', | ||
| true, | ||
| 'Expected dialog to be open' | ||
| ); | ||
| } | ||
| ); | ||
|
|
||
| util.addProperty( | ||
| Assertion.prototype, | ||
| 'closed', | ||
| function (this: typeof Assertion) { | ||
| const obj = util.flag(this, 'object'); | ||
| new Assertion(obj).to.be.instanceof(HTMLDialogElement); | ||
| new Assertion(obj as HTMLDialogElement).has.property( | ||
| 'open', | ||
| false, | ||
| 'Expected dialog to be closed' | ||
| ); | ||
| } | ||
| ); |
There was a problem hiding this comment.
Adding new assertions to determine if a dialog element is open.
I'm putting this here (instead of the @mongodb-js/mocha-config-compass package) since this package is TypeScript and the other is pure JavaScript at the moment.
|
|
||
| await waitForElementToBeRemoved(() => | ||
| screen.getByTestId('assistant-confirm-clear-chat-modal') | ||
| ); |
There was a problem hiding this comment.
FYI this changes the test because waitForRemoved specifically exists to check first that the element exists before being removed, just waitFor doesn't do it, but I guess it's good enough if we checked before
There was a problem hiding this comment.
check first that the element exists before being removed
I agree that does change the test - but also, that behavior seems like a race-condition to me 🤔 It depends on an implementation detail of the userEvent.click(confirmButton); firing (and re-rendering the modal), asynchronously.
This might be an inherent property of the testing library?
There was a problem hiding this comment.
It's not related to testing-library, it depends just on how it's rendered on the screen
| export namespace Chai { | ||
| interface Assertion { | ||
| /** Asserts that a dialog is open */ | ||
| get open(): Assertion; |
There was a problem hiding this comment.
@Anemy FYI for the eslint plugin you are working on in devtools-shared, new terminator assertion
| renderModal(); | ||
| const button = screen.getByText('Confirm').closest('button'); | ||
| const { container } = renderModal(); | ||
| const button = within(container).getByText('Confirm').closest('button'); |
There was a problem hiding this comment.
I'm curious why this change is required: testing-library supposed to clean-up after itself, so there should be no other elements on the screen with text Confirm in them and using screen over returned values from render is the recommended way to use testing-library (not a strict requirement, but still)
There was a problem hiding this comment.
I've reverted this change 👍 It was introduced before mongodb/leafygreen-ui#3189 was merged and released.
There was a problem hiding this comment.
How did we ended up with context menu (and now modal tests) in this component?
There was a problem hiding this comment.
It's also doesn't seem to be used anymore, so might as well just drop this whole thing
There was a problem hiding this comment.
It is the behavior of the new modal to include the dialog element, regardless of the modal being open or not.
It's also doesn't seem to be used anymore
What specifically doesn't seem to be used anymore?
There was a problem hiding this comment.
I agree - the value of this test deteriorated beyond being useful. I'd be happy to delete the two components and their tests all together. Would that suffice?
There was a problem hiding this comment.
Merged the PR - I believe this thread can be resolved 👍
| children: confirmationProps.buttonText ?? 'Confirm', | ||
| onClick: handleConfirm, | ||
| ...confirmationProps.confirmButtonProps, | ||
| 'data-lgid': 'lg-confirmation_modal-footer-confirm_button', |
There was a problem hiding this comment.
lgid doesn't look like something we should be touching or overriding, is there no way we can pass our own id or another data attribute to the confirm button so that we don't depend on leafygreen for this? Can we autoFocus it instead like in the other cases in this patch?
There was a problem hiding this comment.
I reverted this line and updated the initialFocus prop below:
compass/packages/compass-components/src/hooks/use-confirmation.tsx
Lines 204 to 207 in 406b9a4
This aligns with an upcoming change in LG (https://jira.mongodb.org/browse/LG-5735)
| for (const modal of modals) { | ||
| // Ensure any modals are interactable if open | ||
| await modal.waitForClickable({ | ||
| timeout: 500, |
There was a problem hiding this comment.
Unexpected to see defaults overriden in shared helpers, we configure them for the whole test suite via env usually because different environments might require slightly different configuration (we're also pretty generous with those usually especially for CI), so setting it to a value different from default by default doesn't seem right to me: it might be 500ms on your machine, but 5s next time windows host in GHA has issues with performance
There was a problem hiding this comment.
I agree and updated this to pass these options instead of overriding the default:
| ): Promise<void> { | ||
| await browser.waitUntil( | ||
| async () => { | ||
| const modals = await browser.getOpenModals(selector); |
There was a problem hiding this comment.
I would expect that if I'm calling waitForOpenModal, I'm only looking for one modals, weird to see that this code is dealing with multiple ones instead of throwing if it got more than one back. Is this some quirk of how leafygreen renders those that forces us to do this? Is it just always adding a new modal on the screen or something instead of re-rendering?
There was a problem hiding this comment.
Is this some quirk of how leafygreen renders those that forces us to do this?
The selector is often matching a data-testid which is put on the dialog element, which is mounted into the DOM even when the modal is closed. If two components rendering a modal internally is mounted at the same time, multiple elements will match the selector. Waiting for the modal to open then becomes a matter waiting for one of those modals to open.
One alternative I considered was appending a [open] attribute to the selector. I ultimately scrapped that because it felt too brittle. For one, it would not work for selector lists (selectors separated with ,) 🤔 It would be great if WebDriver IO had a way to impose "additional constraints". This is why I'm currently filtering the modals based on their open attribute in getOpenModals:
| initialFocus={ | ||
| confirmationProps.initialFocus ?? | ||
| '[data-lgid=lg-confirmation_modal-footer-confirm_button]' | ||
| } |
There was a problem hiding this comment.
This changes the behavior when confirmation modal is used with required input in an unexpected way and puts focus on a fake-disabled button instead of input as it happens right now. Same for showPrompt where the focus should land on the input we're showing, not on confirm button right away
There was a problem hiding this comment.
showPrompt still doesn't focus on the input
3595882 to
406b9a4
Compare
|
I added a more robust "locator strategy" to find open dialogs: The benefit of this is that all the code to select and filter runs in the browser, instead of passing back and forth between the webdriver client and the browser using async operations. This makes the check much more reliable when elements are entering and leaving the document. |
e822695 to
a8bf853
Compare
… more important with the new dialog
…to move the code later
536b433 to
6fa377d
Compare
I fixed the "bulk update modal issue" with c2e5d46 and updated the confirmation modal to "remove data-testid usage from application logic" 👍 |

COMPASS-9642
Description
This PR upgrades the modal related LG packages to their latests version.
Checklist
Motivation and Context
Open Questions
Dependents
Types of changes