@agent-relay/dashboard 2.0.81 → 2.0.82
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/out/404.html +1 -1
- package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
- package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
- package/out/about.html +2 -2
- package/out/about.txt +1 -1
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +1 -1
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +1 -1
- package/out/changelog.html +2 -2
- package/out/changelog.txt +1 -1
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +1 -1
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +1 -1
- package/out/contact.html +2 -2
- package/out/contact.txt +1 -1
- package/out/docs.html +2 -2
- package/out/docs.txt +1 -1
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +1 -1
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +1 -1
- package/out/privacy.html +2 -2
- package/out/privacy.txt +1 -1
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +1 -1
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +1 -1
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +1 -1
- package/out/providers.html +1 -1
- package/out/providers.txt +1 -1
- package/out/security.html +2 -2
- package/out/security.txt +1 -1
- package/out/signup.html +2 -2
- package/out/signup.txt +1 -1
- package/out/terms.html +2 -2
- package/out/terms.txt +1 -1
- package/package.json +7 -1
- package/src/app/about/page.tsx +7 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
- package/src/app/app/[[...slug]]/page.tsx +23 -0
- package/src/app/app/onboarding/page.tsx +394 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
- package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
- package/src/app/blog/page.tsx +15 -0
- package/src/app/careers/page.tsx +7 -0
- package/src/app/changelog/page.tsx +7 -0
- package/src/app/cloud/link/page.tsx +464 -0
- package/src/app/complete-profile/page.tsx +204 -0
- package/src/app/connect-repos/page.tsx +410 -0
- package/src/app/contact/page.tsx +7 -0
- package/src/app/docs/page.tsx +7 -0
- package/src/app/favicon.png +0 -0
- package/src/app/globals.css +200 -0
- package/src/app/history/page.tsx +658 -0
- package/src/app/layout.tsx +25 -0
- package/src/app/login/page.tsx +424 -0
- package/src/app/metrics/page.tsx +781 -0
- package/src/app/page.tsx +59 -0
- package/src/app/pricing/page.tsx +7 -0
- package/src/app/privacy/page.tsx +7 -0
- package/src/app/providers/page.tsx +193 -0
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
- package/src/app/providers/setup/[provider]/constants.ts +35 -0
- package/src/app/providers/setup/[provider]/page.tsx +42 -0
- package/src/app/security/page.tsx +7 -0
- package/src/app/signup/page.tsx +533 -0
- package/src/app/terms/page.tsx +7 -0
- package/src/components/ActivityFeed.tsx +216 -0
- package/src/components/AddWorkspaceModal.tsx +170 -0
- package/src/components/AgentCard.test.tsx +134 -0
- package/src/components/AgentCard.tsx +585 -0
- package/src/components/AgentList.test.tsx +147 -0
- package/src/components/AgentList.tsx +419 -0
- package/src/components/AgentLogPreview.tsx +173 -0
- package/src/components/AgentProfilePanel.tsx +569 -0
- package/src/components/App.tsx +3424 -0
- package/src/components/BillingPanel.tsx +922 -0
- package/src/components/BillingResult.tsx +447 -0
- package/src/components/BroadcastComposer.tsx +690 -0
- package/src/components/ChannelAdminPanel.tsx +773 -0
- package/src/components/ChannelBrowser.tsx +385 -0
- package/src/components/ChannelChat.tsx +261 -0
- package/src/components/ChannelSidebar.tsx +399 -0
- package/src/components/CloudSessionProvider.tsx +130 -0
- package/src/components/CommandPalette.tsx +815 -0
- package/src/components/ConfirmationDialog.tsx +133 -0
- package/src/components/ConversationHistory.tsx +518 -0
- package/src/components/CoordinatorPanel.tsx +956 -0
- package/src/components/DecisionQueue.tsx +717 -0
- package/src/components/DirectMessageView.tsx +164 -0
- package/src/components/FileAutocomplete.tsx +368 -0
- package/src/components/FleetOverview.tsx +278 -0
- package/src/components/LogViewer.tsx +310 -0
- package/src/components/LogViewerPanel.tsx +482 -0
- package/src/components/Logo.tsx +284 -0
- package/src/components/MentionAutocomplete.tsx +384 -0
- package/src/components/MessageComposer.tsx +473 -0
- package/src/components/MessageList.tsx +725 -0
- package/src/components/MessageSenderName.tsx +91 -0
- package/src/components/MessageStatusIndicator.tsx +142 -0
- package/src/components/NewConversationModal.tsx +400 -0
- package/src/components/NotificationToast.tsx +488 -0
- package/src/components/OnlineUsersIndicator.tsx +164 -0
- package/src/components/Pagination.tsx +124 -0
- package/src/components/PricingPlans.tsx +386 -0
- package/src/components/ProjectList.tsx +711 -0
- package/src/components/ProviderAuthFlow.tsx +343 -0
- package/src/components/ProviderConnectionList.tsx +375 -0
- package/src/components/ProvisioningProgress.tsx +730 -0
- package/src/components/ReactionChips.tsx +70 -0
- package/src/components/ReactionPicker.tsx +121 -0
- package/src/components/RepoAccessPanel.tsx +787 -0
- package/src/components/RepositoriesPanel.tsx +901 -0
- package/src/components/ServerCard.tsx +202 -0
- package/src/components/SessionExpiredModal.tsx +128 -0
- package/src/components/SpawnModal.test.tsx +190 -0
- package/src/components/SpawnModal.tsx +1001 -0
- package/src/components/TaskAssignmentUI.tsx +375 -0
- package/src/components/TerminalProviderSetup.tsx +517 -0
- package/src/components/ThemeProvider.tsx +159 -0
- package/src/components/ThinkingIndicator.tsx +231 -0
- package/src/components/ThreadList.tsx +198 -0
- package/src/components/ThreadPanel.tsx +405 -0
- package/src/components/TrajectoryViewer.tsx +698 -0
- package/src/components/TypingIndicator.tsx +69 -0
- package/src/components/UsageBanner.tsx +231 -0
- package/src/components/UserProfilePanel.tsx +233 -0
- package/src/components/WorkspaceContext.tsx +95 -0
- package/src/components/WorkspaceSelector.tsx +234 -0
- package/src/components/WorkspaceStatusIndicator.tsx +396 -0
- package/src/components/XTermInteractive.tsx +516 -0
- package/src/components/XTermLogViewer.tsx +719 -0
- package/src/components/channels/ChannelDialogs.tsx +1411 -0
- package/src/components/channels/ChannelHeader.tsx +317 -0
- package/src/components/channels/ChannelMessageList.tsx +463 -0
- package/src/components/channels/ChannelViewV1.tsx +146 -0
- package/src/components/channels/MessageInput.tsx +302 -0
- package/src/components/channels/SearchInput.tsx +172 -0
- package/src/components/channels/SearchResults.tsx +336 -0
- package/src/components/channels/api.test.ts +1527 -0
- package/src/components/channels/api.ts +703 -0
- package/src/components/channels/index.ts +76 -0
- package/src/components/channels/mockApi.ts +344 -0
- package/src/components/channels/types.ts +566 -0
- package/src/components/hooks/index.ts +58 -0
- package/src/components/hooks/useAgentLogs.ts +504 -0
- package/src/components/hooks/useAgents.ts +127 -0
- package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
- package/src/components/hooks/useBroadcastDedup.ts +86 -0
- package/src/components/hooks/useChannelAdmin.ts +329 -0
- package/src/components/hooks/useChannelBrowser.ts +239 -0
- package/src/components/hooks/useChannelCommands.ts +138 -0
- package/src/components/hooks/useChannels.ts +367 -0
- package/src/components/hooks/useDebounce.ts +29 -0
- package/src/components/hooks/useDirectMessage.test.ts +952 -0
- package/src/components/hooks/useDirectMessage.ts +141 -0
- package/src/components/hooks/useMessages.ts +310 -0
- package/src/components/hooks/useOrchestrator.test.ts +165 -0
- package/src/components/hooks/useOrchestrator.ts +424 -0
- package/src/components/hooks/usePinnedAgents.test.ts +356 -0
- package/src/components/hooks/usePinnedAgents.ts +140 -0
- package/src/components/hooks/usePresence.test.ts +245 -0
- package/src/components/hooks/usePresence.ts +377 -0
- package/src/components/hooks/useRecentRepos.ts +130 -0
- package/src/components/hooks/useSession.ts +209 -0
- package/src/components/hooks/useThread.ts +138 -0
- package/src/components/hooks/useTrajectory.ts +265 -0
- package/src/components/hooks/useWebSocket.ts +290 -0
- package/src/components/hooks/useWorkspaceMembers.ts +132 -0
- package/src/components/hooks/useWorkspaceRepos.ts +73 -0
- package/src/components/hooks/useWorkspaceStatus.ts +237 -0
- package/src/components/index.ts +81 -0
- package/src/components/layout/Header.tsx +311 -0
- package/src/components/layout/RepoContextHeader.tsx +361 -0
- package/src/components/layout/Sidebar.archive.test.tsx +126 -0
- package/src/components/layout/Sidebar.test.tsx +691 -0
- package/src/components/layout/Sidebar.tsx +900 -0
- package/src/components/layout/index.ts +7 -0
- package/src/components/settings/BillingSettingsPanel.tsx +564 -0
- package/src/components/settings/SettingsPage.tsx +683 -0
- package/src/components/settings/TeamSettingsPanel.tsx +560 -0
- package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
- package/src/components/settings/index.ts +11 -0
- package/src/components/settings/types.ts +79 -0
- package/src/components/utils/messageFormatting.test.tsx +331 -0
- package/src/components/utils/messageFormatting.tsx +597 -0
- package/src/index.ts +63 -0
- package/src/landing/AboutPage.tsx +77 -0
- package/src/landing/BlogContent.tsx +187 -0
- package/src/landing/BlogPage.tsx +47 -0
- package/src/landing/CareersPage.tsx +53 -0
- package/src/landing/ChangelogPage.tsx +33 -0
- package/src/landing/ContactPage.tsx +41 -0
- package/src/landing/DocsPage.tsx +43 -0
- package/src/landing/LandingPage.tsx +702 -0
- package/src/landing/PricingPage.tsx +549 -0
- package/src/landing/PrivacyPage.tsx +117 -0
- package/src/landing/SecurityPage.tsx +42 -0
- package/src/landing/StaticPage.tsx +165 -0
- package/src/landing/TermsPage.tsx +125 -0
- package/src/landing/blogData.ts +312 -0
- package/src/landing/index.ts +18 -0
- package/src/landing/styles.css +3673 -0
- package/src/lib/agent-merge.test.ts +43 -0
- package/src/lib/agent-merge.ts +35 -0
- package/src/lib/api.ts +1294 -0
- package/src/lib/cloudApi.ts +893 -0
- package/src/lib/colors.test.ts +175 -0
- package/src/lib/colors.ts +218 -0
- package/src/lib/config.ts +109 -0
- package/src/lib/hierarchy.ts +242 -0
- package/src/lib/stuckDetection.ts +142 -0
- package/src/lib/useUrlRouting.ts +190 -0
- package/src/types/index.ts +317 -0
- package/src/types/threading.ts +7 -0
- package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
- package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
- /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
- /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
- /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
- /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Components
|
|
3
|
+
*
|
|
4
|
+
* Unified settings UI for dashboard, workspace, team, and billing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { SettingsPage, type SettingsPageProps } from './SettingsPage';
|
|
8
|
+
export { WorkspaceSettingsPanel, type WorkspaceSettingsPanelProps } from './WorkspaceSettingsPanel';
|
|
9
|
+
export { TeamSettingsPanel, type TeamSettingsPanelProps } from './TeamSettingsPanel';
|
|
10
|
+
export { BillingSettingsPanel, type BillingSettingsPanelProps } from './BillingSettingsPanel';
|
|
11
|
+
export { defaultSettings, type Settings } from './types';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Types
|
|
3
|
+
*
|
|
4
|
+
* Dashboard settings types for appearance, notifications, display, and connection preferences.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** CLI type identifiers matching agent templates */
|
|
8
|
+
export type CliType = 'claude' | 'codex' | 'gemini' | 'opencode' | 'droid' | 'cursor' | 'custom';
|
|
9
|
+
|
|
10
|
+
/** Agent spawning default preferences */
|
|
11
|
+
export interface AgentDefaults {
|
|
12
|
+
/** Default CLI type when opening spawn modal (null = show all templates) */
|
|
13
|
+
defaultCliType: CliType | null;
|
|
14
|
+
/** Default models for each CLI type that supports model selection */
|
|
15
|
+
defaultModels: {
|
|
16
|
+
claude: string;
|
|
17
|
+
cursor: string;
|
|
18
|
+
codex: string;
|
|
19
|
+
gemini: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Settings {
|
|
24
|
+
theme: 'light' | 'dark' | 'system';
|
|
25
|
+
notifications: {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
sound: boolean;
|
|
28
|
+
desktop: boolean;
|
|
29
|
+
mentionsOnly: boolean;
|
|
30
|
+
};
|
|
31
|
+
display: {
|
|
32
|
+
compactMode: boolean;
|
|
33
|
+
showTimestamps: boolean;
|
|
34
|
+
showAvatars: boolean;
|
|
35
|
+
animationsEnabled: boolean;
|
|
36
|
+
};
|
|
37
|
+
messages: {
|
|
38
|
+
autoScroll: boolean;
|
|
39
|
+
};
|
|
40
|
+
connection: {
|
|
41
|
+
autoReconnect: boolean;
|
|
42
|
+
reconnectDelay: number;
|
|
43
|
+
keepAliveInterval: number;
|
|
44
|
+
};
|
|
45
|
+
agentDefaults: AgentDefaults;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const defaultSettings: Settings = {
|
|
49
|
+
theme: 'system',
|
|
50
|
+
notifications: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
sound: true,
|
|
53
|
+
desktop: false,
|
|
54
|
+
mentionsOnly: false,
|
|
55
|
+
},
|
|
56
|
+
display: {
|
|
57
|
+
compactMode: false,
|
|
58
|
+
showTimestamps: true,
|
|
59
|
+
showAvatars: true,
|
|
60
|
+
animationsEnabled: true,
|
|
61
|
+
},
|
|
62
|
+
messages: {
|
|
63
|
+
autoScroll: true,
|
|
64
|
+
},
|
|
65
|
+
connection: {
|
|
66
|
+
autoReconnect: true,
|
|
67
|
+
reconnectDelay: 3000,
|
|
68
|
+
keepAliveInterval: 30000,
|
|
69
|
+
},
|
|
70
|
+
agentDefaults: {
|
|
71
|
+
defaultCliType: null,
|
|
72
|
+
defaultModels: {
|
|
73
|
+
claude: 'sonnet',
|
|
74
|
+
cursor: 'opus-4.5-thinking',
|
|
75
|
+
codex: 'gpt-5.2-codex',
|
|
76
|
+
gemini: 'gemini-2.5-pro',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Message Formatting Tests
|
|
7
|
+
*
|
|
8
|
+
* Tests for markdown rendering in messages including:
|
|
9
|
+
* - Inline formatting (bold, italic, strikethrough, code)
|
|
10
|
+
* - Block elements (headings, lists, blockquotes)
|
|
11
|
+
* - Links and URLs
|
|
12
|
+
* - Code blocks and tables
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
17
|
+
import { render } from '@testing-library/react';
|
|
18
|
+
|
|
19
|
+
// Mock react-syntax-highlighter to avoid ESM issues in tests
|
|
20
|
+
vi.mock('react-syntax-highlighter', () => {
|
|
21
|
+
const MockPrismLight = ({ children }: { children: string }) => <pre data-testid="code-block">{children}</pre>;
|
|
22
|
+
MockPrismLight.registerLanguage = vi.fn();
|
|
23
|
+
return { PrismLight: MockPrismLight };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
vi.mock('react-syntax-highlighter/dist/esm/styles/prism', () => ({
|
|
27
|
+
oneDark: {},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/javascript', () => ({ default: {} }));
|
|
31
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/typescript', () => ({ default: {} }));
|
|
32
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/python', () => ({ default: {} }));
|
|
33
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/bash', () => ({ default: {} }));
|
|
34
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/json', () => ({ default: {} }));
|
|
35
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/markdown', () => ({ default: {} }));
|
|
36
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/yaml', () => ({ default: {} }));
|
|
37
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/css', () => ({ default: {} }));
|
|
38
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/go', () => ({ default: {} }));
|
|
39
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/rust', () => ({ default: {} }));
|
|
40
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/sql', () => ({ default: {} }));
|
|
41
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/ruby', () => ({ default: {} }));
|
|
42
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/java', () => ({ default: {} }));
|
|
43
|
+
vi.mock('react-syntax-highlighter/dist/esm/languages/prism/docker', () => ({ default: {} }));
|
|
44
|
+
|
|
45
|
+
import { formatMessageBody } from './messageFormatting.js';
|
|
46
|
+
|
|
47
|
+
describe('messageFormatting', () => {
|
|
48
|
+
describe('inline formatting', () => {
|
|
49
|
+
it('renders bold text with double asterisks', () => {
|
|
50
|
+
const { container } = render(<>{formatMessageBody('This is **bold** text')}</>);
|
|
51
|
+
const strong = container.querySelector('strong');
|
|
52
|
+
expect(strong).not.toBeNull();
|
|
53
|
+
expect(strong?.textContent).toBe('bold');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('renders bold text with double underscores', () => {
|
|
57
|
+
const { container } = render(<>{formatMessageBody('This is __bold__ text')}</>);
|
|
58
|
+
const strong = container.querySelector('strong');
|
|
59
|
+
expect(strong).not.toBeNull();
|
|
60
|
+
expect(strong?.textContent).toBe('bold');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('renders italic text with single asterisks', () => {
|
|
64
|
+
const { container } = render(<>{formatMessageBody('This is *italic* text')}</>);
|
|
65
|
+
const em = container.querySelector('em');
|
|
66
|
+
expect(em).not.toBeNull();
|
|
67
|
+
expect(em?.textContent).toBe('italic');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders italic text with single underscores', () => {
|
|
71
|
+
const { container } = render(<>{formatMessageBody('This is _italic_ text')}</>);
|
|
72
|
+
const em = container.querySelector('em');
|
|
73
|
+
expect(em).not.toBeNull();
|
|
74
|
+
expect(em?.textContent).toBe('italic');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('renders strikethrough text', () => {
|
|
78
|
+
const { container } = render(<>{formatMessageBody('This is ~~deleted~~ text')}</>);
|
|
79
|
+
const del = container.querySelector('del');
|
|
80
|
+
expect(del).not.toBeNull();
|
|
81
|
+
expect(del?.textContent).toBe('deleted');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders inline code', () => {
|
|
85
|
+
const { container } = render(<>{formatMessageBody('Use `npm install` to install')}</>);
|
|
86
|
+
const code = container.querySelector('code');
|
|
87
|
+
expect(code).not.toBeNull();
|
|
88
|
+
expect(code?.textContent).toBe('npm install');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('handles multiple inline formats in one line', () => {
|
|
92
|
+
const { container } = render(
|
|
93
|
+
<>{formatMessageBody('**Bold** and *italic* and `code`')}</>
|
|
94
|
+
);
|
|
95
|
+
expect(container.querySelector('strong')?.textContent).toBe('Bold');
|
|
96
|
+
expect(container.querySelector('em')?.textContent).toBe('italic');
|
|
97
|
+
expect(container.querySelector('code')?.textContent).toBe('code');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles nested bold within text', () => {
|
|
101
|
+
const { container } = render(
|
|
102
|
+
<>{formatMessageBody('Start **middle** end')}</>
|
|
103
|
+
);
|
|
104
|
+
expect(container.textContent).toBe('Start middle end');
|
|
105
|
+
expect(container.querySelector('strong')?.textContent).toBe('middle');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('links', () => {
|
|
110
|
+
it('renders markdown links', () => {
|
|
111
|
+
const { container } = render(
|
|
112
|
+
<>{formatMessageBody('Check out [Google](https://google.com)')}</>
|
|
113
|
+
);
|
|
114
|
+
const link = container.querySelector('a');
|
|
115
|
+
expect(link).not.toBeNull();
|
|
116
|
+
expect(link?.textContent).toBe('Google');
|
|
117
|
+
expect(link?.getAttribute('href')).toBe('https://google.com');
|
|
118
|
+
expect(link?.getAttribute('target')).toBe('_blank');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('auto-links URLs', () => {
|
|
122
|
+
const { container } = render(
|
|
123
|
+
<>{formatMessageBody('Visit https://example.com for more')}</>
|
|
124
|
+
);
|
|
125
|
+
const link = container.querySelector('a');
|
|
126
|
+
expect(link).not.toBeNull();
|
|
127
|
+
expect(link?.getAttribute('href')).toBe('https://example.com');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('auto-links http URLs', () => {
|
|
131
|
+
const { container } = render(
|
|
132
|
+
<>{formatMessageBody('Visit http://example.com')}</>
|
|
133
|
+
);
|
|
134
|
+
const link = container.querySelector('a');
|
|
135
|
+
expect(link?.getAttribute('href')).toBe('http://example.com');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('headings', () => {
|
|
140
|
+
it('renders h1 headings', () => {
|
|
141
|
+
const { container } = render(<>{formatMessageBody('# Main Title')}</>);
|
|
142
|
+
const heading = container.querySelector('div');
|
|
143
|
+
expect(heading?.textContent).toBe('Main Title');
|
|
144
|
+
expect(heading?.className).toContain('font-bold');
|
|
145
|
+
expect(heading?.className).toContain('border-b');
|
|
146
|
+
expect(heading?.style.fontSize).toBe('1.5rem');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('renders h2 headings', () => {
|
|
150
|
+
const { container } = render(<>{formatMessageBody('## Section')}</>);
|
|
151
|
+
const heading = container.querySelector('div');
|
|
152
|
+
expect(heading?.textContent).toBe('Section');
|
|
153
|
+
expect(heading?.style.fontSize).toBe('1.25rem');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('renders h3 headings', () => {
|
|
157
|
+
const { container } = render(<>{formatMessageBody('### Subsection')}</>);
|
|
158
|
+
const heading = container.querySelector('div');
|
|
159
|
+
expect(heading?.textContent).toBe('Subsection');
|
|
160
|
+
expect(heading?.style.fontSize).toBe('1.125rem');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('renders heading with inline formatting', () => {
|
|
164
|
+
const { container } = render(<>{formatMessageBody('## **Bold** Heading')}</>);
|
|
165
|
+
const strong = container.querySelector('strong');
|
|
166
|
+
expect(strong?.textContent).toBe('Bold');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('lists', () => {
|
|
171
|
+
it('renders bullet list with dash', () => {
|
|
172
|
+
const { container } = render(<>{formatMessageBody('- Item one')}</>);
|
|
173
|
+
expect(container.textContent).toContain('•');
|
|
174
|
+
expect(container.textContent).toContain('Item one');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('renders bullet list with asterisk', () => {
|
|
178
|
+
const { container } = render(<>{formatMessageBody('* Item one')}</>);
|
|
179
|
+
expect(container.textContent).toContain('•');
|
|
180
|
+
expect(container.textContent).toContain('Item one');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('renders numbered list', () => {
|
|
184
|
+
const { container } = render(<>{formatMessageBody('1. First item')}</>);
|
|
185
|
+
expect(container.textContent).toContain('1.');
|
|
186
|
+
expect(container.textContent).toContain('First item');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('renders list item with inline formatting', () => {
|
|
190
|
+
const { container } = render(<>{formatMessageBody('- **Bold** item')}</>);
|
|
191
|
+
expect(container.querySelector('strong')?.textContent).toBe('Bold');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('blockquotes', () => {
|
|
196
|
+
it('renders blockquote', () => {
|
|
197
|
+
const { container } = render(<>{formatMessageBody('> This is quoted')}</>);
|
|
198
|
+
const blockquote = container.querySelector('blockquote');
|
|
199
|
+
expect(blockquote).not.toBeNull();
|
|
200
|
+
expect(blockquote?.textContent).toBe('This is quoted');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('renders blockquote with space after >', () => {
|
|
204
|
+
const { container } = render(<>{formatMessageBody('> Quoted text')}</>);
|
|
205
|
+
const blockquote = container.querySelector('blockquote');
|
|
206
|
+
expect(blockquote?.textContent).toBe('Quoted text');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('renders multi-line blockquote', () => {
|
|
210
|
+
const { container } = render(
|
|
211
|
+
<>{formatMessageBody('> Line one\n> Line two')}</>
|
|
212
|
+
);
|
|
213
|
+
const blockquote = container.querySelector('blockquote');
|
|
214
|
+
expect(blockquote?.textContent).toContain('Line one');
|
|
215
|
+
expect(blockquote?.textContent).toContain('Line two');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('code blocks', () => {
|
|
220
|
+
it('renders fenced code block', () => {
|
|
221
|
+
const { container } = render(
|
|
222
|
+
<>{formatMessageBody('```javascript\nconst x = 1;\n```')}</>
|
|
223
|
+
);
|
|
224
|
+
// Code blocks are rendered with syntax highlighter
|
|
225
|
+
expect(container.textContent).toContain('const x = 1;');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('renders code block without language', () => {
|
|
229
|
+
const { container } = render(
|
|
230
|
+
<>{formatMessageBody('```\nplain code\n```')}</>
|
|
231
|
+
);
|
|
232
|
+
expect(container.textContent).toContain('plain code');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('tables', () => {
|
|
237
|
+
it('renders table-like content', () => {
|
|
238
|
+
const tableContent = '| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |';
|
|
239
|
+
const { container } = render(<>{formatMessageBody(tableContent)}</>);
|
|
240
|
+
const pre = container.querySelector('pre');
|
|
241
|
+
expect(pre).not.toBeNull();
|
|
242
|
+
expect(pre?.textContent).toContain('Header 1');
|
|
243
|
+
expect(pre?.textContent).toContain('Cell 1');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('mentions', () => {
|
|
248
|
+
it('highlights mentions when provided', () => {
|
|
249
|
+
const { container } = render(
|
|
250
|
+
<>{formatMessageBody('Hello @alice and @bob', { mentions: ['alice', 'bob'] })}</>
|
|
251
|
+
);
|
|
252
|
+
const mentions = container.querySelectorAll('span.bg-accent-cyan\\/20');
|
|
253
|
+
expect(mentions.length).toBe(2);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('does not highlight non-matching mentions', () => {
|
|
257
|
+
const { container } = render(
|
|
258
|
+
<>{formatMessageBody('Hello @charlie', { mentions: ['alice'] })}</>
|
|
259
|
+
);
|
|
260
|
+
const mentions = container.querySelectorAll('span.bg-accent-cyan\\/20');
|
|
261
|
+
expect(mentions.length).toBe(0);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('mixed content', () => {
|
|
266
|
+
it('handles complex markdown message', () => {
|
|
267
|
+
const content = `# Title
|
|
268
|
+
|
|
269
|
+
**Bold** and *italic* text.
|
|
270
|
+
|
|
271
|
+
- List item 1
|
|
272
|
+
- List item 2
|
|
273
|
+
|
|
274
|
+
> A quote
|
|
275
|
+
|
|
276
|
+
\`\`\`typescript
|
|
277
|
+
const x = 1;
|
|
278
|
+
\`\`\`
|
|
279
|
+
|
|
280
|
+
Visit https://example.com`;
|
|
281
|
+
|
|
282
|
+
const { container } = render(<>{formatMessageBody(content)}</>);
|
|
283
|
+
|
|
284
|
+
// Check various elements exist
|
|
285
|
+
expect(container.querySelector('strong')).not.toBeNull();
|
|
286
|
+
expect(container.querySelector('em')).not.toBeNull();
|
|
287
|
+
expect(container.querySelector('blockquote')).not.toBeNull();
|
|
288
|
+
expect(container.querySelector('a')).not.toBeNull();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('preserves newlines in regular text', () => {
|
|
292
|
+
const { container } = render(
|
|
293
|
+
<>{formatMessageBody('Line 1\nLine 2\nLine 3')}</>
|
|
294
|
+
);
|
|
295
|
+
const brs = container.querySelectorAll('br');
|
|
296
|
+
expect(brs.length).toBe(2);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('handles escaped newlines', () => {
|
|
300
|
+
const { container } = render(
|
|
301
|
+
<>{formatMessageBody('Line 1\\nLine 2')}</>
|
|
302
|
+
);
|
|
303
|
+
const brs = container.querySelectorAll('br');
|
|
304
|
+
expect(brs.length).toBe(1);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('edge cases', () => {
|
|
309
|
+
it('handles empty string', () => {
|
|
310
|
+
const { container } = render(<>{formatMessageBody('')}</>);
|
|
311
|
+
expect(container.textContent).toBe('');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('handles plain text without formatting', () => {
|
|
315
|
+
const { container } = render(<>{formatMessageBody('Just plain text')}</>);
|
|
316
|
+
expect(container.textContent).toBe('Just plain text');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('handles unclosed formatting markers', () => {
|
|
320
|
+
const { container } = render(<>{formatMessageBody('**unclosed bold')}</>);
|
|
321
|
+
// Should not crash, renders as-is
|
|
322
|
+
expect(container.textContent).toContain('**unclosed bold');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('handles special characters', () => {
|
|
326
|
+
const { container } = render(<>{formatMessageBody('Test < > & " \'')}</>);
|
|
327
|
+
expect(container.textContent).toContain('<');
|
|
328
|
+
expect(container.textContent).toContain('>');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
});
|