fix(editor): Prevent Reka UI from interfering with Element Plus dropdown selections (#23370)

This commit is contained in:
Csaba Tuncsik
2025-12-18 15:56:58 +01:00
committed by GitHub
parent 04636cc365
commit f700aff583
3 changed files with 88 additions and 0 deletions
@@ -7,6 +7,8 @@ import {
PopoverRoot,
type PopoverRootProps,
PopoverTrigger,
type PointerDownOutsideEvent,
type FocusOutsideEvent,
} from 'reka-ui';
import { watch } from 'vue';
import type { CSSProperties } from 'vue';
@@ -89,6 +91,21 @@ function handleOpenAutoFocus(e: Event) {
}
}
/**
* Handles outside interaction events to prevent Reka UI from interfering
* with Element Plus dropdown selections. Element Plus teleports dropdowns
* to the body, so Reka UI's DismissableLayer detects clicks on them as
* "outside" clicks, which would otherwise prevent proper selection.
*
* TODO: This workaround can be removed once N8nSelect is migrated to Reka UI.
*/
function handleOutsideInteraction(e: PointerDownOutsideEvent | FocusOutsideEvent) {
const target = e.target as HTMLElement | null;
if (target?.closest('.el-popper, .el-select-dropdown')) {
e.preventDefault();
}
}
// Watch open state to emit lifecycle events
watch(
() => props.open,
@@ -118,6 +135,8 @@ watch(
:style="{ width, zIndex }"
:reference="reference"
@open-auto-focus="handleOpenAutoFocus"
@pointer-down-outside="handleOutsideInteraction"
@interact-outside="handleOutsideInteraction"
>
<N8nScrollArea
v-if="enableScrolling"
@@ -86,4 +86,26 @@ export class ExecutionsPage extends BasePage {
await this.page.getByTestId('execution-preview-delete-button').click();
await this.page.locator('button.btn--confirm').click();
}
// Filter methods
getFilterButton(): Locator {
return this.page.getByTestId('executions-filter-button');
}
getFilterForm(): Locator {
return this.page.getByTestId('execution-filter-form');
}
getStatusSelect(): Locator {
return this.page.getByTestId('executions-filter-status-select');
}
async openFilter(): Promise<void> {
await this.getFilterButton().click();
}
async selectStatus(status: string): Promise<void> {
await this.getStatusSelect().click();
await this.page.locator('.el-select-dropdown__item').filter({ hasText: status }).click();
}
}
@@ -1,6 +1,53 @@
import { test, expect } from '../../../../fixtures/base';
import executionOutOfMemoryResponse from '../../../../fixtures/execution-out-of-memory-server-response.json';
test.describe('Executions Filter', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
});
test('should keep popover open when selecting from dropdown inside it', async ({ n8n }) => {
// Regression test: Element Plus dropdowns are teleported to body, causing
// Reka UI's DismissableLayer to detect clicks as "outside" and close the popover.
// This test verifies the popover stays open during and after dropdown selection.
// Create some executions first
await n8n.executionsComposer.createExecutions(2);
// Go to executions tab
await n8n.canvas.clickExecutionsTab();
await expect(n8n.executions.getExecutionItems().first()).toBeVisible();
// Open filter popover
await n8n.executions.openFilter();
const filterForm = n8n.executions.getFilterForm();
await expect(filterForm).toBeVisible();
// Click to open the status dropdown
await n8n.executions.getStatusSelect().click();
// Verify popover is still open while dropdown is open
await expect(filterForm).toBeVisible();
// Set up listener for the filtered executions request
const filterRequestPromise = n8n.page.waitForRequest(
(request) =>
request.url().includes('/rest/executions?filter=') && request.url().includes('success'),
);
// Select an option from the dropdown
await n8n.page.locator('.el-select-dropdown__item').filter({ hasText: 'Success' }).click();
// Verify the filter request was sent to the backend (confirms selection worked)
const filterRequest = await filterRequestPromise;
expect(filterRequest.url()).toContain('status');
expect(filterRequest.url()).toContain('success');
// KEY ASSERTION: Verify the popover did NOT close after selecting from dropdown
await expect(filterForm).toBeVisible();
});
});
const ERROR_MESSAGES = {
OUT_OF_MEMORY: 'Workflow did not finish, possible out-of-memory issue',
} as const;