Skip to content

Commit 6a3c025

Browse files
Copilotmofojed
andauthored
fix: Update browser title in embed-widget to show widget name (#2559)
Update browser title when widget is opened in embed-widget component ## Summary Implemented browser title update feature for the embed-widget component with minimal, focused changes. **Changes:** - Added a `useEffect` hook in `App.tsx` that updates `document.title` to `<widgetName> - Deephaven` when a widget name is provided via the URL parameter - Created comprehensive tests in `App.test.tsx` to verify the functionality works correctly - Updated tests to use `TestUtils.createMockProxy<typeof DhType>()` for creating mock API objects (fixed TypeScript types) - Improved test consistency by using `waitFor` instead of `setTimeout` in all test cases - Fixed window.location mocking to use `Object.defineProperty` instead of type casting with `any` **Example:** When accessing the embed-widget with URL `?name=foobar`, the browser title will be updated to "foobar - Deephaven" **Files changed:** - `packages/embed-widget/src/App.tsx` - Added useEffect hook for title update (10 lines added) - `packages/embed-widget/src/App.test.tsx` - Added tests for the new functionality (96 lines, new file) <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > Open a PR to update the title in the browser when using the embed-widget component. The title should be the name of the opened widget with "- Deephaven" appended. For example, if I open a widget named "foobar" in embed-widget, the browser title should be updated to be "foobar - Deephaven" </details> <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mofojed <4505624+mofojed@users.noreply.github.com>
1 parent f3eb538 commit 6a3c025

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from 'react';
2+
import { render, waitFor } from '@testing-library/react';
3+
import { Provider } from 'react-redux';
4+
import { createMockStore } from '@deephaven/redux';
5+
import { TestUtils } from '@deephaven/test-utils';
6+
import { ApiContext } from '@deephaven/jsapi-bootstrap';
7+
import type { dh as DhType } from '@deephaven/jsapi-types';
8+
import App from './App';
9+
10+
// Mock the modules that App depends on
11+
jest.mock('@deephaven/app-utils', () => ({
12+
AppDashboards: () => <div>AppDashboards</div>,
13+
GrpcLayoutStorage: jest.fn(),
14+
LocalWorkspaceStorage: jest.fn(() => ({
15+
load: jest.fn().mockResolvedValue({
16+
data: { settings: {} },
17+
}),
18+
})),
19+
useConnection: jest.fn(() => ({
20+
getObject: jest.fn(),
21+
getStorageService: jest.fn(),
22+
})),
23+
useServerConfig: jest.fn(() => ({})),
24+
useUser: jest.fn(() => ({ name: 'test-user' })),
25+
}));
26+
27+
jest.mock('@deephaven/dashboard', () => ({
28+
getAllDashboardsData: jest.fn(() => ({})),
29+
setDashboardPluginData: jest.fn(),
30+
emitPanelOpen: jest.fn(),
31+
useCreateDashboardListener: jest.fn(),
32+
}));
33+
34+
jest.mock('@deephaven/plugin', () => ({
35+
useDashboardPlugins: jest.fn(() => []),
36+
}));
37+
38+
jest.mock('@deephaven/jsapi-bootstrap', () => ({
39+
...jest.requireActual('@deephaven/jsapi-bootstrap'),
40+
getVariableDescriptor: jest.fn(),
41+
useApi: jest.fn(() => ({})),
42+
useClient: jest.fn(() => ({
43+
getStorageService: jest.fn(() => ({})),
44+
})),
45+
}));
46+
47+
jest.mock('@deephaven/jsapi-utils', () => ({
48+
fetchVariableDefinition: jest.fn(),
49+
}));
50+
51+
describe('App', () => {
52+
const mockApi = TestUtils.createMockProxy<typeof DhType>();
53+
let store: ReturnType<typeof createMockStore>;
54+
55+
beforeEach(() => {
56+
// Create a minimal Redux store
57+
store = createMockStore();
58+
59+
// Reset document.title before each test
60+
document.title = 'Deephaven Embedded Widget';
61+
62+
// Mock window.location.search
63+
Object.defineProperty(window, 'location', {
64+
value: {
65+
search: '?name=testWidget',
66+
},
67+
writable: true,
68+
});
69+
});
70+
71+
it('should update document title with widget name from URL parameter', async () => {
72+
render(
73+
<Provider store={store}>
74+
<ApiContext.Provider value={mockApi}>
75+
<App />
76+
</ApiContext.Provider>
77+
</Provider>
78+
);
79+
80+
await waitFor(() => {
81+
expect(document.title).toBe('testWidget - Deephaven');
82+
});
83+
});
84+
85+
it('should not update document title when name parameter is missing', async () => {
86+
Object.defineProperty(window, 'location', {
87+
value: {
88+
search: '',
89+
},
90+
writable: true,
91+
});
92+
93+
render(
94+
<Provider store={store}>
95+
<ApiContext.Provider value={mockApi}>
96+
<App />
97+
</ApiContext.Provider>
98+
</Provider>
99+
);
100+
101+
await waitFor(() => {
102+
expect(document.title).toBe('Deephaven Embedded Widget');
103+
});
104+
});
105+
});

packages/embed-widget/src/App.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ function App(): JSX.Element {
7575
const dispatch = useDispatch();
7676
const serverConfig = useServerConfig();
7777

78+
// Update the browser title when the widget name is available
79+
useEffect(
80+
function updateTitle() {
81+
if (name != null) {
82+
document.title = `${name} - Deephaven`;
83+
}
84+
},
85+
[name]
86+
);
87+
7888
useEffect(
7989
function initializeApp() {
8090
async function initApp(): Promise<void> {

0 commit comments

Comments
 (0)