diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js
index be9b469a5..7f3cce3a9 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js
@@ -25,7 +25,7 @@ const EnvironmentListContent = ({
No Environment
onEnvironmentSelect(env)}
+ data-tooltip-id="environment-name-tooltip"
data-tooltip-content={env.name}
data-tooltip-hidden={env.name?.length < 90}
>
diff --git a/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/StyledWrapper.js
index e2ce8e423..3c27a00f4 100644
--- a/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/StyledWrapper.js
+++ b/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/StyledWrapper.js
@@ -2,10 +2,20 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
border-bottom: 1px solid ${(props) => props.theme.border.border0};
- transition: opacity 0.15s ease;
+
+ /* Dim the row content when disabled, but not the tooltip */
+ .accordion-left > :not(.toolhint),
+ .accordion-actions,
+ .accordion-body {
+ transition: opacity 0.15s ease;
+ }
&.disabled {
- opacity: 0.45;
+ .accordion-left > :not(.toolhint),
+ .accordion-actions,
+ .accordion-body {
+ opacity: 0.45;
+ }
}
.accordion-header {
@@ -24,6 +34,12 @@ const StyledWrapper = styled.div`
min-width: 0;
color: ${(props) => props.theme.text};
+ .message-label-anchor {
+ display: flex;
+ min-width: 0;
+ overflow: hidden;
+ }
+
.message-label {
font-size: ${(props) => props.theme.font.size.sm};
cursor: text;
diff --git a/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js b/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js
index ebf9e1d04..eb1ebba83 100644
--- a/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js
+++ b/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js
@@ -5,7 +5,7 @@ import { get } from 'lodash';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
-import React, { useMemo, useState, useEffect, useRef, useCallback } from 'react';
+import React, { useMemo, useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { queueWsMessage, isWsConnectionActive, connectWS } from 'utils/network/index';
import { findCollectionByUid, findEnvironmentInCollection } from 'utils/collections/index';
@@ -53,6 +53,7 @@ export const SingleWSMessage = ({
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState(displayName);
+ const labelTooltipId = `ws-msg-label-${message.uid ?? index}`;
// Auto-focus the name input when this is a newly created message
useEffect(() => {
@@ -204,17 +205,27 @@ export const SingleWSMessage = ({
onClick={(e) => e.stopPropagation()}
/>
) : (
- {
- e.preventDefault();
- onToggle();
- }}
- onDoubleClick={handleNameClick}
+
- {displayName}
-
+ {
+ e.preventDefault();
+ onToggle();
+ }}
+ onDoubleClick={handleNameClick}
+ >
+ {displayName}
+
+
)}
e.stopPropagation()}>
diff --git a/packages/bruno-app/src/components/ToolHint/index.js b/packages/bruno-app/src/components/ToolHint/index.js
index 70d1baba7..79c3fb6a4 100644
--- a/packages/bruno-app/src/components/ToolHint/index.js
+++ b/packages/bruno-app/src/components/ToolHint/index.js
@@ -5,6 +5,7 @@ import { useTheme } from 'providers/Theme';
const ToolHint = ({
text,
toolhintId,
+ tooltipId,
anchorSelect,
children,
tooltipStyle = {},
@@ -15,7 +16,8 @@ const ToolHint = ({
theme = null,
className = '',
delayShow = 200,
- dataTestId
+ dataTestId,
+ tooltipTestId
}) => {
const { theme: contextTheme } = useTheme();
const appliedTheme = theme || contextTheme;
@@ -32,17 +34,21 @@ const ToolHint = ({
color: toolhintTextColor
};
- const toolhintProps_final = anchorSelect
- ? { anchorSelect }
- : { anchorId: toolhintId };
+ const usesExternalAnchor = Boolean(tooltipId || anchorSelect);
+ const toolhintProps_final = tooltipId
+ ? { id: tooltipId }
+ : anchorSelect
+ ? { anchorSelect }
+ : { anchorId: toolhintId };
return (
<>
- {!anchorSelect && {children}}
- {anchorSelect && children}
+ {!usesExternalAnchor && {children}}
+ {usesExternalAnchor && children}
{content} : undefined}
className="toolhint"
offset={offset}
place={place}
diff --git a/tests/utils/page/actions.ts b/tests/utils/page/actions.ts
index 27dc2e383..3a78a36aa 100644
--- a/tests/utils/page/actions.ts
+++ b/tests/utils/page/actions.ts
@@ -1,7 +1,7 @@
import { test, expect, Page, ElectronApplication, waitForReadyPage as waitForReadyPageImpl } from '../../../playwright';
import process from 'node:process';
import * as path from 'path';
-import { buildCommonLocators, buildScriptErrorLocators, buildGrpcCommonLocators } from './locators';
+import { buildCommonLocators, buildScriptErrorLocators, buildGrpcCommonLocators, buildWebsocketCommonLocators } from './locators';
import { waitForCollectionMount } from './mounting';
type SandboxMode = 'safe' | 'developer';
@@ -1937,6 +1937,24 @@ const generateCollectionDocs = async (
});
};
+/**
+ * Rename a websocket message by double-clicking its label and typing a new name.
+ * @param page - The page object
+ * @param index - The zero-based index of the message in the list
+ * @param name - The new message name
+ */
+const renameWsMessage = async (page: Page, index: number, name: string) => {
+ await test.step(`Rename websocket message ${index} to "${name}"`, async () => {
+ const ws = buildWebsocketCommonLocators(page);
+ await ws.message.label(index).dblclick();
+ const nameInput = ws.message.nameInput(index);
+ await expect(nameInput).toBeVisible();
+ await nameInput.selectText();
+ await page.keyboard.type(name);
+ await nameInput.press('Enter');
+ });
+};
+
export {
waitForReadyPage,
dismissImportIssuesToasts,
@@ -2013,7 +2031,8 @@ export {
closeGenerateCodeDialog,
openRequestInFolder,
setUrlEncoding,
- generateCollectionDocs
+ generateCollectionDocs,
+ renameWsMessage
};
export type { SandboxMode, EnvironmentType, EnvironmentVariable, ImportCollectionOptions, CreateRequestOptions, CreateUntitledRequestOptions, CreateTransientRequestOptions, AssertionInput };
diff --git a/tests/utils/page/locators.ts b/tests/utils/page/locators.ts
index 70e007cf0..57b958b5e 100644
--- a/tests/utils/page/locators.ts
+++ b/tests/utils/page/locators.ts
@@ -238,6 +238,11 @@ export const buildWebsocketCommonLocators = (page: Page) => ({
.filter({ hasText: /^Close Connection$/ })
},
messages: () => page.locator('.ws-message'),
+ message: {
+ label: (index: number) => page.getByTestId(`ws-message-label-${index}`),
+ nameInput: (index: number) => page.getByTestId(`ws-message-name-input-${index}`),
+ nameTooltip: () => page.getByTestId('ws-message-name-tooltip')
+ },
toolbar: {
latestFirst: () => page.getByRole('button', { name: 'Latest First' }),
latestLast: () => page.getByRole('button', { name: 'Latest Last' }),
diff --git a/tests/websockets/multi-message-bru/message-name-tooltip.spec.ts b/tests/websockets/multi-message-bru/message-name-tooltip.spec.ts
new file mode 100644
index 000000000..f46a589a1
--- /dev/null
+++ b/tests/websockets/multi-message-bru/message-name-tooltip.spec.ts
@@ -0,0 +1,46 @@
+import { expect, test } from '../../../playwright';
+import { openRequest, closeAllCollections, renameWsMessage } from '../../utils/page/actions';
+import { buildWebsocketCommonLocators } from '../../utils/page/locators';
+
+const COLLECTION_NAME = 'ws-multi-message';
+const SINGLE_MSG_REQ = 'ws-single-msg';
+
+const LONG_NAME
+ = 'this is a very long websocket message name that should be truncated with an ellipsis';
+
+test.describe('websocket message name tooltip', () => {
+ test.afterAll(async ({ pageWithUserData: page }) => {
+ await closeAllCollections(page);
+ });
+
+ test('shows the full name in a tooltip on hover', async ({
+ pageWithUserData: page
+ }) => {
+ const ws = buildWebsocketCommonLocators(page);
+ await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ);
+
+ const messageLabel = ws.message.label(0);
+ const tooltip = ws.message.nameTooltip();
+
+ await test.step('short name → tooltip shows the name on hover', async () => {
+ await renameWsMessage(page, 0, 'hi');
+ await expect(messageLabel).toHaveText('hi');
+
+ await messageLabel.hover();
+ await expect(tooltip).toBeVisible({ timeout: 15000 });
+ await expect(tooltip).toContainText('hi');
+ });
+
+ await test.step('long name → label is truncated and tooltip reveals the full name', async () => {
+ await renameWsMessage(page, 0, LONG_NAME);
+ await expect(messageLabel).toHaveText(LONG_NAME);
+ // the label itself is clipped with an ellipsis...
+ await expect(messageLabel).toHaveCSS('text-overflow', 'ellipsis');
+
+ // ...and hovering surfaces the full name in the tooltip
+ await messageLabel.hover();
+ await expect(tooltip).toBeVisible({ timeout: 15000 });
+ await expect(tooltip).toContainText(LONG_NAME);
+ });
+ });
+});
diff --git a/tests/websockets/multi-message-yml/message-name-tooltip.spec.ts b/tests/websockets/multi-message-yml/message-name-tooltip.spec.ts
new file mode 100644
index 000000000..49d85911a
--- /dev/null
+++ b/tests/websockets/multi-message-yml/message-name-tooltip.spec.ts
@@ -0,0 +1,46 @@
+import { expect, test } from '../../../playwright';
+import { openRequest, closeAllCollections, renameWsMessage } from '../../utils/page/actions';
+import { buildWebsocketCommonLocators } from '../../utils/page/locators';
+
+const COLLECTION_NAME = 'ws-multi-message-yml';
+const SINGLE_MSG_REQ = 'ws-single-msg';
+
+const LONG_NAME
+ = 'this is a very long websocket message name that should be truncated with an ellipsis';
+
+test.describe('websocket message name tooltip (yml format)', () => {
+ test.afterAll(async ({ pageWithUserData: page }) => {
+ await closeAllCollections(page);
+ });
+
+ test('shows the full name in a tooltip on hover', async ({
+ pageWithUserData: page
+ }) => {
+ const ws = buildWebsocketCommonLocators(page);
+ await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ);
+
+ const messageLabel = ws.message.label(0);
+ const tooltip = ws.message.nameTooltip();
+
+ await test.step('short name → tooltip shows the name on hover', async () => {
+ await renameWsMessage(page, 0, 'hi');
+ await expect(messageLabel).toHaveText('hi');
+
+ await messageLabel.hover();
+ await expect(tooltip).toBeVisible({ timeout: 15000 });
+ await expect(tooltip).toContainText('hi');
+ });
+
+ await test.step('long name → label is truncated and tooltip reveals the full name', async () => {
+ await renameWsMessage(page, 0, LONG_NAME);
+ await expect(messageLabel).toHaveText(LONG_NAME);
+ // the label itself is clipped with an ellipsis...
+ await expect(messageLabel).toHaveCSS('text-overflow', 'ellipsis');
+
+ // ...and hovering surfaces the full name in the tooltip
+ await messageLabel.hover();
+ await expect(tooltip).toBeVisible({ timeout: 15000 });
+ await expect(tooltip).toContainText(LONG_NAME);
+ });
+ });
+});