@flamingo-stack/openframe-frontend-core 0.0.675-snapshot.20260928180045 → 0.0.675
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REGRESSION: an app-screen link in a chat answer must open IN the app.
|
|
3
|
-
*
|
|
4
|
-
* The hub writes the screens of the app the chat is embedded in as
|
|
5
|
-
* ROOT-RELATIVE markdown links (`[Devices](/devices)`). A host-mode runtime
|
|
6
|
-
* (the OpenFrame app) must open each one in the same tab through its own
|
|
7
|
-
* `navigate` — a router push, no reload, no second tab.
|
|
8
|
-
*
|
|
9
|
-
* The same link written ABSOLUTE, even to the page's own origin, opens a new
|
|
10
|
-
* tab: the new-tab rule is textual (`isCrossOriginUrl`), which is why the hub
|
|
11
|
-
* writes these links relative. The contrast case pins that, so a change on
|
|
12
|
-
* either side of the contract shows up here.
|
|
13
|
-
*
|
|
14
|
-
* This drives the real bubble, the real markdown engine and the real anchor
|
|
15
|
-
* (`NavLinkAnchorViaRuntime`), with the runtime wired the way the OpenFrame
|
|
16
|
-
* app wires it (`navigate` router-pushes a same-origin href; `decideNewTab`
|
|
17
|
-
* is the lib rule with no target platform).
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
21
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
22
|
-
import { type ChatRuntime, ChatRuntimeContext } from '../../../contexts/chat-runtime-context';
|
|
23
|
-
import { ChatMessageEnhanced } from '../chat-message-enhanced';
|
|
24
|
-
import { NavLinkAnchorViaRuntime } from '../nav-link-anchor-via-runtime';
|
|
25
|
-
import { stripSameOriginToPath } from '../utils/chat-nav-resolution';
|
|
26
|
-
import { decideNewTab } from '../utils/decide-new-tab';
|
|
27
|
-
import { isCrossOriginUrl } from '../utils/is-cross-origin-url';
|
|
28
|
-
|
|
29
|
-
const routerPush = vi.fn<(path: string) => void>();
|
|
30
|
-
|
|
31
|
-
vi.mock('../../../embed-shims/next-navigation', () => ({
|
|
32
|
-
usePathname: () => '/dashboard',
|
|
33
|
-
useRouter: () => ({ back: vi.fn(), prefetch: vi.fn(), push: routerPush, replace: vi.fn() }),
|
|
34
|
-
useSearchParams: () => new URLSearchParams(),
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
/** jsdom's own origin — `location.origin` is undefined there, `location.href` is not. */
|
|
38
|
-
const ORIGIN = new URL(window.location.href).origin;
|
|
39
|
-
|
|
40
|
-
const ANSWER = [
|
|
41
|
-
'Get the install command from [Devices → Add Device](/devices/new): pick the customer and the platform.',
|
|
42
|
-
'',
|
|
43
|
-
'Then go to [Devices](/devices) and check the **Status** column. For a searchable log of all activity, use [Logs](/logs-page).',
|
|
44
|
-
].join('\n');
|
|
45
|
-
|
|
46
|
-
const openExternal = vi.fn<(href: string) => void>();
|
|
47
|
-
const windowOpen = vi.spyOn(window, 'open').mockImplementation(() => null);
|
|
48
|
-
|
|
49
|
-
const runtime = {
|
|
50
|
-
endpoints: { chatStreamUrl: '/content/api/docs/chat' },
|
|
51
|
-
navigation: {
|
|
52
|
-
decideNewTab: ({ href }: { href: string }) =>
|
|
53
|
-
decideNewTab({ currentSource: 'openframe', href, targetPlatform: null }),
|
|
54
|
-
mode: 'host',
|
|
55
|
-
navigate: ({ href }: { href: string }) => {
|
|
56
|
-
if (isCrossOriginUrl(href)) return false;
|
|
57
|
-
routerPush(stripSameOriginToPath(href));
|
|
58
|
-
return true;
|
|
59
|
-
},
|
|
60
|
-
openExternal,
|
|
61
|
-
},
|
|
62
|
-
source: 'openframe',
|
|
63
|
-
} as unknown as ChatRuntime;
|
|
64
|
-
|
|
65
|
-
function renderAnswer(text: string) {
|
|
66
|
-
return render(
|
|
67
|
-
<ChatRuntimeContext.Provider value={runtime}>
|
|
68
|
-
<ChatMessageEnhanced
|
|
69
|
-
NavLinkAnchor={NavLinkAnchorViaRuntime}
|
|
70
|
-
content={[{ text, type: 'text' }]}
|
|
71
|
-
renderEntityCard={() => null}
|
|
72
|
-
role="assistant"
|
|
73
|
-
/>
|
|
74
|
-
</ChatRuntimeContext.Provider>,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
beforeEach(() => {
|
|
79
|
-
routerPush.mockClear();
|
|
80
|
-
openExternal.mockClear();
|
|
81
|
-
windowOpen.mockClear();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('ChatMessageEnhanced — in-app screen links', () => {
|
|
85
|
-
it('opens every root-relative screen link in the same tab through the host router', () => {
|
|
86
|
-
renderAnswer(ANSWER);
|
|
87
|
-
const anchors = screen.getAllByRole('link');
|
|
88
|
-
|
|
89
|
-
expect(anchors.map(a => [a.textContent, a.getAttribute('href'), a.getAttribute('target')])).toEqual([
|
|
90
|
-
['Devices → Add Device', '/devices/new', null],
|
|
91
|
-
['Devices', '/devices', null],
|
|
92
|
-
['Logs', '/logs-page', null],
|
|
93
|
-
]);
|
|
94
|
-
|
|
95
|
-
for (const anchor of anchors) fireEvent.click(anchor, { button: 0 });
|
|
96
|
-
|
|
97
|
-
expect(routerPush.mock.calls.map(call => call[0])).toEqual(['/devices/new', '/devices', '/logs-page']);
|
|
98
|
-
expect(openExternal).not.toHaveBeenCalled();
|
|
99
|
-
expect(windowOpen).not.toHaveBeenCalled();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('opens the same link written absolute in a new tab, even on the page origin', () => {
|
|
103
|
-
renderAnswer(ANSWER.replace(/\]\(\//g, `](${ORIGIN}/`));
|
|
104
|
-
const anchor = screen.getByRole('link', { name: 'Devices → Add Device' });
|
|
105
|
-
|
|
106
|
-
expect(anchor.getAttribute('href')).toBe(`${ORIGIN}/devices/new`);
|
|
107
|
-
expect(anchor.getAttribute('target')).toBe('_blank');
|
|
108
|
-
|
|
109
|
-
fireEvent.click(anchor, { button: 0 });
|
|
110
|
-
|
|
111
|
-
expect(openExternal).toHaveBeenCalledWith(`${ORIGIN}/devices/new`);
|
|
112
|
-
expect(routerPush).not.toHaveBeenCalled();
|
|
113
|
-
});
|
|
114
|
-
});
|