-
Notifications
You must be signed in to change notification settings - Fork 39
feat(atomic): migrate atomic-popover from Stencil to Lit #6638
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
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: jmazzah-coveo <229375403+jmazzah-coveo@users.noreply.github.com>
Co-authored-by: jmazzah-coveo <229375403+jmazzah-coveo@users.noreply.github.com>
Co-authored-by: jmazzah-coveo <229375403+jmazzah-coveo@users.noreply.github.com>
β¦ress tests Co-authored-by: jmazzah-coveo <229375403+jmazzah-coveo@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Have describe blocks with method names ex describe #render and test them in order. Also, bring the render method above the private methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! Tests are now organized with method-based describe blocks (#initialize, #render, #disconnectedCallback, #togglePopover), and the component's render method has been moved above the private methods.
Changes in commit aa2676f.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot have a describe with private method, test them throught the public methods that call them, and put the test in the describe of the public method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reorganized tests to remove the #togglePopover describe block (private method). The private togglePopover() method is now tested through the public render method's interactions:
when button is clicked- tests button interactionwhen backdrop is clicked- tests backdrop interactionwhen Escape key is pressed- tests keyboard interaction
All tests remain in the #render describe block since that's where these user interactions are rendered.
Changes in commit fe3074a.
β¦ order Co-authored-by: jmazzah-coveo <229375403+jmazzah-coveo@users.noreply.github.com>
Co-authored-by: jmazzah-coveo <229375403+jmazzah-coveo@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR migrates the atomic-popover component from Stencil to Lit, following the established migration pattern for Atomic components. The component displays a single facet as a popover menu with a button trigger, backdrop, and keyboard support.
Key changes:
- Converted component from Stencil (.tsx) to Lit (.ts) with Tailwind styling
- Migrated from Stencil lifecycle methods to Lit lifecycle callbacks
- Updated from Stencil's
@Component,@State,@Listento Lit's@customElement,@state, and event listeners inconnectedCallback - Replaced functional button helper with
renderButtonfunctional component - Added comprehensive unit tests using Vitest browser mode
- Added E2E tests using Playwright with page object pattern
- Created Storybook documentation with MSW API mocking
- Removed legacy Cypress test suite
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
atomic-popover.tsx (removed) |
Removed original Stencil implementation |
atomic-popover.pcss (removed) |
Removed PostCSS styles (now using Tailwind) |
atomic-popover.ts (new) |
New Lit component implementation with Tailwind styles |
atomic-popover.spec.ts (new) |
Vitest unit tests with browser mode assertions |
atomic-popover.new.stories.tsx (new) |
Storybook stories with MSW API mocking for facet data |
atomic-popover.mdx (new) |
Component documentation for Storybook |
e2e/atomic-popover.e2e.ts (new) |
Playwright E2E tests for user interactions |
e2e/page-object.ts (new) |
Page object pattern for E2E test selectors |
e2e/fixture.ts (new) |
Test fixture setup for E2E tests |
cypress/e2e/facets/popover-facet/* (removed) |
Removed legacy Cypress test suite (4 files) |
components.d.ts |
Auto-generated type definitions updated to remove Stencil types |
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| beforeEach: async () => { | ||
| searchApiHarness.searchEndpoint.clear(); | ||
| }, |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The beforeEach hook calls searchApiHarness.searchEndpoint.clear() but this is unnecessary. According to MSW API mocking guidelines, you only need to call clear() when you have queued responses with mockOnce() that you want to flush. Since this story file doesn't use mockOnce() anywhere, and the base mock at lines 10-58 uses mock() (which is permanent), the clear() call doesn't do anything and should be removed.
| describe('when no child is provided', () => { | ||
| it('should set error', async () => { | ||
| const element = ( | ||
| await renderInAtomicSearchInterface<AtomicPopover>({ | ||
| template: html`<atomic-popover></atomic-popover>`, | ||
| selector: 'atomic-popover', | ||
| bindings: (bindings) => { | ||
| bindings.engine = mockEngine; | ||
| return bindings; | ||
| }, | ||
| }) | ||
| ).element; | ||
|
|
||
| expect(element.error).toBeTruthy(); | ||
| expect(element.error.message).toContain( | ||
| 'One child is required inside a set of popover tags.' | ||
| ); | ||
| }); | ||
| }); | ||
| }); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the error case when more than one child is provided. The component's initialize() method (lines 85-89 of atomic-popover.ts) validates this condition and sets an error, but there's no test verifying this behavior.
Add a test similar to the existing "when no child is provided" test:
describe('when more than one child is provided', () => {
it('should set error', async () => {
const element = (await renderInAtomicSearchInterface<AtomicPopover>({
template: html`<atomic-popover>
<div>Child 1</div>
<div>Child 2</div>
</atomic-popover>`,
selector: 'atomic-popover',
bindings: (bindings) => {
bindings.engine = mockEngine;
return bindings;
},
})).element;
expect(element.error).toBeTruthy();
expect(element.error.message).toContain(
'Cannot have more than one child inside a set of popover tags.'
);
});
});| className="AtomicPopover" | ||
| > | ||
|
|
||
| The `atomic-popover` component displays facets as compact popover menus. When a user clicks the popover button, a backdrop appears along with a popover containing one or more facets. |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation description says "displays facets" (plural) but the component only supports a single facet child. The validation in initialize() (lines 77-89 of atomic-popover.ts) enforces exactly one child. The description should be changed to "displays a facet" (singular) for accuracy.
Also, the phrase "one or more facets" at line 15 is incorrect and should be "a single facet" or just "a facet".
| The `atomic-popover` component displays facets as compact popover menus. When a user clicks the popover button, a backdrop appears along with a popover containing one or more facets. | |
| The `atomic-popover` component displays a facet as a compact popover menu. When a user clicks the popover button, a backdrop appears along with a popover containing a single facet. |
| test.describe('atomic-popover', () => { | ||
| test.beforeEach(async ({popover}) => { | ||
| await popover.load({story: 'default'}); | ||
| await popover.hydrated.waitFor(); | ||
| }); | ||
|
|
||
| test('should open and close popover with button', async ({popover}) => { | ||
| await expect(popover.facetContainer).not.toBeVisible(); | ||
|
|
||
| await popover.popoverButton.click(); | ||
| await expect(popover.facetContainer).toBeVisible(); | ||
| await expect(popover.backdrop).toBeVisible(); | ||
|
|
||
| await popover.popoverButton.click(); | ||
| await expect(popover.facetContainer).not.toBeVisible(); | ||
| }); | ||
|
|
||
| test('should close popover with Escape key', async ({popover}) => { | ||
| await popover.popoverButton.click(); | ||
| await expect(popover.facetContainer).toBeVisible(); | ||
|
|
||
| await popover.page.keyboard.press('Escape'); | ||
| await expect(popover.facetContainer).not.toBeVisible(); | ||
| }); | ||
| }); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing accessibility tests. According to E2E testing guidelines, components should include accessibility testing. The popover component has several accessibility features (aria-expanded, aria-controls, focus trap, keyboard navigation) that should be verified.
Consider adding tests for:
aria-expandedattribute correctly reflects open/closed statearia-controlspoints to the correct popover ID- Keyboard focus management with the focus trap
- Proper role and labeling of the button
- Tab order when popover is open vs closed
You could add a test using toMatchAriaSnapshot to verify the accessibility tree structure.
| this.isOpen = false; | ||
| } | ||
|
|
||
| private toggleOpen() { |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method name toggleOpen() is inconsistent with naming conventions used elsewhere in the codebase. In the Stencil version (line 107 of the removed file), this was togglePopover(), which better describes what it does. Naming should use a verb that clearly indicates the action being performed. Consider renaming to togglePopover() for better clarity and consistency with the original implementation, or at least to toggle() to match the pattern of other similar methods.
| private toggleOpen() { | |
| private togglePopover() { |
| test('should open and close popover with button', async ({popover}) => { | ||
| await expect(popover.facetContainer).not.toBeVisible(); | ||
|
|
||
| await popover.popoverButton.click(); | ||
| await expect(popover.facetContainer).toBeVisible(); | ||
| await expect(popover.backdrop).toBeVisible(); | ||
|
|
||
| await popover.popoverButton.click(); | ||
| await expect(popover.facetContainer).not.toBeVisible(); | ||
| }); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test should use test.step() to group the interactions into logical steps. According to Playwright test writing guidelines, using test.step() improves test readability and reporting by grouping related interactions.
For example, the test at lines 9-18 could be:
test('should open and close popover with button', async ({popover}) => {
await test.step('Verify initial closed state', async () => {
await expect(popover.facetContainer).not.toBeVisible();
});
await test.step('Open popover', async () => {
await popover.popoverButton.click();
await expect(popover.facetContainer).toBeVisible();
await expect(popover.backdrop).toBeVisible();
});
await test.step('Close popover', async () => {
await popover.popoverButton.click();
await expect(popover.facetContainer).not.toBeVisible();
});
});| /** | ||
| * Wait for component to be stable before screenshot | ||
| */ | ||
| async waitForVisualStability() { | ||
| await this.hydrated.waitFor(); | ||
| await this.page.waitForTimeout(500); // Wait for animations | ||
| await this.page.evaluate(() => document.fonts.ready); // Wait for fonts | ||
| await this.page.waitForLoadState('networkidle'); | ||
| } |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment "Wait for component to be stable before screenshot" is misleading. The method doesn't take a screenshotβit only prepares for one by waiting for stability. The captureScreenshot() method below (line 50) is what actually takes the screenshot. Consider renaming this method to waitForStability() or updating the comment to say "Wait for component to be stable (useful before taking screenshots or assertions)".
| describe('#initialize', () => { | ||
| it('should build search status controller', async () => { | ||
| await renderPopover(); | ||
| expect(buildSearchStatus).toHaveBeenCalledWith(mockEngine); | ||
| }); | ||
|
|
||
| describe('when no child is provided', () => { | ||
| it('should set error', async () => { | ||
| const element = ( | ||
| await renderInAtomicSearchInterface<AtomicPopover>({ | ||
| template: html`<atomic-popover></atomic-popover>`, | ||
| selector: 'atomic-popover', | ||
| bindings: (bindings) => { | ||
| bindings.engine = mockEngine; | ||
| return bindings; | ||
| }, | ||
| }) | ||
| ).element; | ||
|
|
||
| expect(element.error).toBeTruthy(); | ||
| expect(element.error.message).toContain( | ||
| 'One child is required inside a set of popover tags.' | ||
| ); | ||
| }); | ||
| }); | ||
| }); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the handleInitializePopover event handler logic. While the event is dispatched in the renderPopover helper (lines 90-96), there's no test verifying:
- That the
childFacetis set when the event is received - That the
popoverClassis added to the child element (line 250 of atomic-popover.ts) - That subsequent events are ignored when
childFacetis already set (line 245 of atomic-popover.ts) - That events without detail are ignored (line 245 of atomic-popover.ts)
Add tests in a new describe('#handleInitializePopover') block to verify this behavior.
| test('should close popover with Escape key', async ({popover}) => { | ||
| await popover.popoverButton.click(); | ||
| await expect(popover.facetContainer).toBeVisible(); | ||
|
|
||
| await popover.page.keyboard.press('Escape'); | ||
| await expect(popover.facetContainer).not.toBeVisible(); | ||
| }); | ||
| }); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test for closing the popover by clicking the backdrop. The component supports closing the popover by clicking the backdrop (line 223 of atomic-popover.ts), but this interaction is not tested in the E2E tests. Add a test case:
test('should close popover when clicking backdrop', async ({popover}) => {
await test.step('Open popover', async () => {
await popover.popoverButton.click();
await expect(popover.facetContainer).toBeVisible();
});
await test.step('Close by clicking backdrop', async () => {
await popover.backdrop.click();
await expect(popover.facetContainer).not.toBeVisible();
});
});| get backdrop() { | ||
| return page.getByTestId('backdrop'); | ||
| }, |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backdrop locator uses getByTestId('backdrop'), but the component doesn't have a data-testid="backdrop" attribute. The backdrop element only has part="backdrop" (line 221 of atomic-popover.ts). This locator will fail to find the element.
Use a part-based selector instead, similar to the parts object pattern defined below. The locator is also never used in the tests, so it should either be removed or used in tests that currently query the backdrop directly via parts(element).backdrop.
| get backdrop() { | |
| return page.getByTestId('backdrop'); | |
| }, |
β Checklist
.mdxfileindex.tsandlazy-index.tsfiles.https://coveord.atlassian.net/browse/KIT-4953