mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-29 07:34:07 +00:00
temp: temp commit to cherry pick
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
||||
IconUpload
|
||||
} from '@tabler/icons';
|
||||
import OpenAPISyncIcon from 'components/Icons/OpenAPISync';
|
||||
import { switchWorkspace, renameWorkspaceAction, exportWorkspaceAction, confirmWorkspaceCreation, cancelWorkspaceCreation } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||
import { switchWorkspace, renameWorkspaceAction, shareWorkspaceAction, confirmWorkspaceCreation, cancelWorkspaceCreation } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||
import { updateWorkspace } from 'providers/ReduxStore/slices/workspaces';
|
||||
import { showInFolder } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
|
||||
@@ -25,6 +25,7 @@ import Dropdown from 'components/Dropdown';
|
||||
import MenuDropdown from 'ui/MenuDropdown';
|
||||
import CloseWorkspace from 'components/Sidebar/CloseWorkspace';
|
||||
import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
|
||||
import ShareWorkspace from 'components/ShareWorkspace';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
|
||||
@@ -55,6 +56,7 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
const [workspaceNameError, setWorkspaceNameError] = useState('');
|
||||
const [closeWorkspaceModalOpen, setCloseWorkspaceModalOpen] = useState(false);
|
||||
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
|
||||
const [shareWorkspaceModalOpen, setShareWorkspaceModalOpen] = useState(false);
|
||||
|
||||
const switcherRef = useRef();
|
||||
const workspaceActionsRef = useRef();
|
||||
@@ -256,20 +258,10 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportWorkspace = () => {
|
||||
const handleShareWorkspace = () => {
|
||||
workspaceActionsRef.current?.hide();
|
||||
const uid = currentWorkspace?.uid;
|
||||
if (!uid) return;
|
||||
|
||||
dispatch(exportWorkspaceAction(uid))
|
||||
.then((result) => {
|
||||
if (!result?.canceled) {
|
||||
toast.success('Workspace exported successfully');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error?.message || 'Error exporting workspace');
|
||||
});
|
||||
if (!currentWorkspace?.uid) return;
|
||||
setShareWorkspaceModalOpen(true);
|
||||
};
|
||||
|
||||
const validateWorkspaceName = (name) => {
|
||||
@@ -401,6 +393,12 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
onClose={() => setCloseWorkspaceModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{shareWorkspaceModalOpen && currentWorkspace?.uid && (
|
||||
<ShareWorkspace
|
||||
workspaceUid={currentWorkspace.uid}
|
||||
onClose={() => setShareWorkspaceModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{createWorkspaceModalOpen && (
|
||||
<CreateWorkspace onClose={handleAdvancedCreateClose} />
|
||||
@@ -544,7 +542,7 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
|
||||
</div>
|
||||
<span>{getRevealInFolderLabel()}</span>
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={handleExportWorkspace}>
|
||||
<div className="dropdown-item" onClick={handleShareWorkspace}>
|
||||
<div className="dropdown-icon">
|
||||
<IconUpload size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.opencollection-link {
|
||||
color: ${(props) => props.theme.textLink};
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-description {
|
||||
font-size: 0.875rem;
|
||||
color: ${(props) => props.theme.colors.text.body};
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.embed-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.embed-remote-url-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
background-color: ${(props) => props.theme.background.secondary};
|
||||
border: 1px solid ${(props) => props.theme.border.border0};
|
||||
|
||||
.embed-remote-icon {
|
||||
color: ${(props) => props.theme.colors.text.warning};
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.embed-remote-url {
|
||||
font-size: 0.875rem;
|
||||
color: ${(props) => props.theme.colors.text.body};
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-tabs-row {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.embed-code-wrap {
|
||||
position: relative;
|
||||
|
||||
.embed-code-container,
|
||||
.code-container {
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
border: 1px solid ${(props) => props.theme.border.border0};
|
||||
background-color: ${(props) => props.theme.background.secondary};
|
||||
overflow: auto;
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.embed-copy-btn {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.375rem;
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
color: ${(props) => props.theme.colors.text.subtext0};
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.background.base};
|
||||
color: ${(props) => props.theme.colors.text.body};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.embed-warning-box {
|
||||
padding: 1rem;
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
background-color: ${(props) => props.theme.status.warning.background};
|
||||
color: ${(props) => props.theme.status.warning.text};
|
||||
border: 1px solid ${(props) => props.theme.status.warning.border};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,112 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { IconCopy, IconGitBranch } from '@tabler/icons';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import toast from 'react-hot-toast';
|
||||
import CodeEditor from 'components/CodeEditor/index';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { escapeHtml } from 'utils/response';
|
||||
import { Tabs, TabsList, TabsTrigger } from 'components/Tabs';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const FETCH_BASE = 'https://fetch.usebruno.com';
|
||||
|
||||
const EMBED_CODE_TABS = [
|
||||
{ value: 'html', label: 'HTML' },
|
||||
{ value: 'markdown', label: 'Markdown' }
|
||||
];
|
||||
|
||||
// Escape so the URL can't break out of the attribute when the snippet is pasted into HTML.
|
||||
const getHtmlEmbedCode = (gitRemoteUrl) => {
|
||||
if (!gitRemoteUrl) return '';
|
||||
return `<!-- Fetch in Bruno Button (Workspace) -->
|
||||
<div class="bruno-fetch-button" data-bruno-collection-url="${escapeHtml(gitRemoteUrl)}"></div>
|
||||
<script src="${FETCH_BASE}/button.js"></script>`;
|
||||
};
|
||||
|
||||
const getMarkdownEmbedCode = (gitRemoteUrl) => gitRemoteUrl
|
||||
? `[<img src="${FETCH_BASE}/button.svg" alt="Fetch in Bruno" style="width: 130px; height: 30px;" width="128" height="32">](${FETCH_BASE}/?url=${encodeURIComponent(gitRemoteUrl)} "target=_blank rel=noopener noreferrer")`
|
||||
: '';
|
||||
|
||||
const EmbedWorkspace = ({ workspace }) => {
|
||||
const { displayedTheme } = useTheme();
|
||||
const [embedTab, setEmbedTab] = useState('html');
|
||||
const [gitRemoteUrl, setGitRemoteUrl] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspace?.pathname || !window.ipcRenderer) return;
|
||||
window.ipcRenderer
|
||||
.invoke('renderer:get-collection-git-details', workspace.pathname)
|
||||
.then((data) => {
|
||||
console.log('data', data);
|
||||
if (data?.gitRootPath && data?.gitRepoUrl) {
|
||||
setGitRemoteUrl(data.gitRepoUrl.trim());
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [workspace?.pathname]);
|
||||
|
||||
const embedCode = embedTab === 'html' ? getHtmlEmbedCode(gitRemoteUrl) : getMarkdownEmbedCode(gitRemoteUrl);
|
||||
|
||||
if (!gitRemoteUrl) {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<p className="embed-description">
|
||||
Embed a Fetch in Bruno button in README's, your website, or anywhere you want to make it easy for developers to clone and run your workspace. Learn more about{' '}
|
||||
<a href="https://docs.usebruno.com/to/embed-bruno-collection" target="_blank" rel="noopener noreferrer" className="opencollection-link">
|
||||
Fetch in Bruno
|
||||
</a>
|
||||
</p>
|
||||
<div className="embed-warning-box">
|
||||
⚠️ Creating an embedded 'Fetch in Bruno' button requires a synchronized local and remote Git repository
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="embed-section">
|
||||
<p className="embed-description">
|
||||
Embed a Fetch in Bruno button in README's, your website, or anywhere you want to make it easy for developers to clone and run your workspace. Learn more about{' '}
|
||||
<a href="https://docs.usebruno.com/git-integration/embed-bruno-collection" target="_blank" rel="noopener noreferrer" className="opencollection-link">
|
||||
Fetch in Bruno
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="embed-remote-url-card">
|
||||
<IconGitBranch size={18} strokeWidth={1.5} className="embed-remote-icon" />
|
||||
<span className="embed-remote-url">{gitRemoteUrl}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="embed-tabs-row">
|
||||
<Tabs value={embedTab} onValueChange={setEmbedTab}>
|
||||
<TabsList>
|
||||
{EMBED_CODE_TABS.map((tab) => (
|
||||
<TabsTrigger key={tab.value} value={tab.value}>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<div className="embed-code-wrap">
|
||||
<div className="embed-code-container code-container">
|
||||
<CodeEditor
|
||||
value={embedCode}
|
||||
theme={displayedTheme}
|
||||
mode={embedTab === 'html' ? 'text/html' : 'text/x-markdown'}
|
||||
/>
|
||||
</div>
|
||||
<CopyToClipboard text={embedCode} onCopy={() => toast.success('Copied to clipboard!')}>
|
||||
<button type="button" className="embed-copy-btn">
|
||||
<IconCopy size={18} strokeWidth={1.5} />
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedWorkspace;
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Button from 'ui/Button';
|
||||
import toast from 'react-hot-toast';
|
||||
import { shareWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||
|
||||
const ExportWorkspace = ({ workspace, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
|
||||
const handleExportZip = async () => {
|
||||
if (!workspace?.uid || isExporting) return;
|
||||
|
||||
setIsExporting(true);
|
||||
try {
|
||||
const result = await dispatch(shareWorkspaceAction(workspace.uid));
|
||||
if (!result?.canceled) {
|
||||
toast.success('Workspace exported successfully');
|
||||
onClose();
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(err?.message || 'Error exporting workspace');
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 mb-4">
|
||||
Export this workspace as a ZIP file to back up or share with others.
|
||||
</p>
|
||||
<div className="modal-footer">
|
||||
<Button size="sm" onClick={handleExportZip} disabled={isExporting} loading={isExporting}>
|
||||
{isExporting ? 'Exporting...' : 'Export as ZIP'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExportWorkspace;
|
||||
57
packages/bruno-app/src/components/ShareWorkspace/index.js
Normal file
57
packages/bruno-app/src/components/ShareWorkspace/index.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { IconUpload, IconCode } from '@tabler/icons';
|
||||
import Modal from 'components/Modal';
|
||||
import StyledWrapper from 'components/ShareCollection/StyledWrapper';
|
||||
import classnames from 'classnames';
|
||||
import ExportWorkspace from './ExportWorkspace';
|
||||
import EmbedWorkspace from './EmbedWorkspace';
|
||||
|
||||
const ShareWorkspace = ({ onClose, workspaceUid }) => {
|
||||
const workspaces = useSelector((state) => state.workspaces.workspaces);
|
||||
const workspace = workspaces.find((w) => w.uid === workspaceUid);
|
||||
const [tab, setTab] = useState('export');
|
||||
|
||||
const handleTabSelect = (value) => (e) => setTab(value);
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`flex tab items-center py-2 px-4 ${tabName}`, {
|
||||
active: tabName === tab
|
||||
});
|
||||
};
|
||||
|
||||
const renderTabContent = () => {
|
||||
switch (tab) {
|
||||
case 'export':
|
||||
return <ExportWorkspace workspace={workspace} onClose={onClose} />;
|
||||
case 'embed':
|
||||
return <EmbedWorkspace workspace={workspace} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (!workspace) return null;
|
||||
|
||||
return (
|
||||
<Modal size="md" title="Export Workspace" handleCancel={onClose} hideFooter>
|
||||
<StyledWrapper className="flex flex-col h-full w-full">
|
||||
<div className="flex w-full mb-6">
|
||||
<div className="inline-flex tabs">
|
||||
<div className={getTabClassname('export')} onClick={handleTabSelect('export')}>
|
||||
<IconUpload size={18} strokeWidth={1.5} className="mr-2" />
|
||||
Export
|
||||
</div>
|
||||
<div className={getTabClassname('embed')} onClick={handleTabSelect('embed')}>
|
||||
<IconCode size={18} strokeWidth={1.5} className="mr-2" />
|
||||
Embed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{renderTabContent()}
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShareWorkspace;
|
||||
@@ -835,7 +835,7 @@ export const copyWorkspaceEnvironment = (workspaceUid, environmentUid, newName)
|
||||
};
|
||||
};
|
||||
|
||||
export const exportWorkspaceAction = (workspaceUid) => {
|
||||
export const shareWorkspaceAction = (workspaceUid) => {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const { workspaces } = getState().workspaces;
|
||||
|
||||
@@ -6,13 +6,47 @@ const getAppProtocolUrlFromArgv = (argv) => {
|
||||
};
|
||||
|
||||
// Handle app protocol URLs
|
||||
const handleAppProtocolUrl = (url) => {
|
||||
// Handle OAuth2 callback URLs - `bruno://app/oauth2/callback`
|
||||
if (isOauth2Url(url)) {
|
||||
handleOauth2ProtocolUrl(url);
|
||||
function handleAppProtocolUrl(url, mainWindow) {
|
||||
try {
|
||||
const workspaceRepositoryUrl = getWorkspaceRepositoryUrl(url);
|
||||
|
||||
console.log('workspaceRepositoryUrl', workspaceRepositoryUrl);
|
||||
if (workspaceRepositoryUrl) {
|
||||
mainWindow?.webContents?.send?.('main:bruno-workspace-git-url-import', workspaceRepositoryUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle OAuth2 callback URLs - `bruno://app/oauth2/callback`
|
||||
if (isOauth2Url(url)) {
|
||||
handleOauth2ProtocolUrl(url);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Unsupported Bruno Deeplink URL');
|
||||
} catch (error) {
|
||||
console.error('Invalid protocol URL:', url, error?.message);
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
function getWorkspaceRepositoryUrl(url) {
|
||||
try {
|
||||
if (!url || typeof url !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(url);
|
||||
const { pathname: parsedUrlPathname } = parsedUrl;
|
||||
|
||||
if (parsedUrlPathname !== '/workspace/import/git') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsedUrl?.searchParams?.get?.('url') || null;
|
||||
} catch (error) {
|
||||
console.error('Failed to parse deep link URL:', error?.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const isOauth2Url = (url) => {
|
||||
try {
|
||||
@@ -27,4 +61,9 @@ const isOauth2Url = (url) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = { handleAppProtocolUrl, getAppProtocolUrlFromArgv };
|
||||
module.exports = {
|
||||
getAppProtocolUrlFromArgv,
|
||||
handleAppProtocolUrl,
|
||||
getWorkspaceRepositoryUrl,
|
||||
isOauth2Url
|
||||
};
|
||||
|
||||
95
packages/bruno-electron/src/utils/deeplink.spec.js
Normal file
95
packages/bruno-electron/src/utils/deeplink.spec.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const { getCollectionRepositoryUrl, getWorkspaceRepositoryUrl, getAppProtocolUrlFromArgv, handleAppProtocolUrl, getOpenApiSpecUrl } = require('./deeplink');
|
||||
|
||||
describe('Deeplink URL Functions', () => {
|
||||
describe('getAppProtocolUrlFromArgv', () => {
|
||||
it('should return the first valid deeplink URL from argv', () => {
|
||||
const argv = ['some-command', 'bruno://app/collection/import/git?url=https://github.com/user/repo'];
|
||||
expect(getAppProtocolUrlFromArgv(argv)).toBe('bruno://app/collection/import/git?url=https://github.com/user/repo');
|
||||
});
|
||||
|
||||
it('should return undefined if no valid deeplink URL is found', () => {
|
||||
const argv = ['some-command', 'random-string'];
|
||||
expect(getAppProtocolUrlFromArgv(argv)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWorkspaceRepositoryUrl', () => {
|
||||
it('should extract the repository URL from a valid workspace deeplink URL', () => {
|
||||
const url = 'bruno://app/workspace/import/git?url=https://github.com/user/workspace-repo';
|
||||
expect(getWorkspaceRepositoryUrl(url)).toBe('https://github.com/user/workspace-repo');
|
||||
});
|
||||
|
||||
it('should return null for null input', () => {
|
||||
expect(getWorkspaceRepositoryUrl(null)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for non-string input', () => {
|
||||
expect(getWorkspaceRepositoryUrl(42)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for collection path', () => {
|
||||
const url = 'bruno://app/collection/import/git?url=https://github.com/user/repo';
|
||||
expect(getWorkspaceRepositoryUrl(url)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for invalid workspace path', () => {
|
||||
const url = 'bruno://app/workspace/invalid/path?url=https://github.com/user/repo';
|
||||
expect(getWorkspaceRepositoryUrl(url)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if no URL parameter is present', () => {
|
||||
const url = 'bruno://app/workspace/import/git';
|
||||
expect(getWorkspaceRepositoryUrl(url)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleAppProtocolUrl', () => {
|
||||
let mockSend;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSend = jest.fn();
|
||||
global.mainWindow = { webContents: { send: mockSend } };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.mainWindow = undefined;
|
||||
});
|
||||
|
||||
it('should send the extracted URL to the main process if valid', () => {
|
||||
const url = 'bruno://app/collection/import/git?url=https://github.com/user/repo';
|
||||
handleAppProtocolUrl(url, global.mainWindow);
|
||||
expect(mockSend).toHaveBeenCalledWith('main:bruno-collection-git-url-import', 'https://github.com/user/repo');
|
||||
});
|
||||
|
||||
it('should send workspace URL to main:bruno-workspace-git-url-import for workspace deeplink', () => {
|
||||
const url = 'bruno://app/workspace/import/git?url=https://github.com/user/workspace-repo';
|
||||
handleAppProtocolUrl(url, global.mainWindow);
|
||||
expect(mockSend).toHaveBeenCalledWith('main:bruno-workspace-git-url-import', 'https://github.com/user/workspace-repo');
|
||||
});
|
||||
|
||||
it('should send the extracted OpenAPI spec URL to the main process if valid', () => {
|
||||
const url = 'bruno://app/collection/import/openapi?url=https://example.com/api-spec.json';
|
||||
handleAppProtocolUrl(url, global.mainWindow);
|
||||
expect(mockSend).toHaveBeenCalledWith('main:bruno-openapi-spec-url-import', 'https://example.com/api-spec.json');
|
||||
});
|
||||
|
||||
it('should log an error for an unsupported deeplink URL', () => {
|
||||
console.error = jest.fn();
|
||||
const url = 'bruno://app/collection/invalid/path?url=https://github.com/user/repo';
|
||||
handleAppProtocolUrl(url);
|
||||
expect(console.error).toHaveBeenCalledWith('Unsupported Bruno Deeplink URL');
|
||||
});
|
||||
|
||||
it('should log an error for an invalid URL', () => {
|
||||
console.error = jest.fn();
|
||||
const url = 'invalid-url';
|
||||
handleAppProtocolUrl(url);
|
||||
expect(console.error).toHaveBeenCalledTimes(5);
|
||||
expect(console.error).toHaveBeenNthCalledWith(1, 'Failed to parse deep link URL:', 'Invalid URL');
|
||||
expect(console.error).toHaveBeenNthCalledWith(2, 'Failed to parse deep link URL:', 'Invalid URL');
|
||||
expect(console.error).toHaveBeenNthCalledWith(3, 'Failed to parse deep link URL:', 'Invalid URL');
|
||||
expect(console.error).toHaveBeenNthCalledWith(4, 'Failed to parse deep link URL:', 'Invalid URL');
|
||||
expect(console.error).toHaveBeenNthCalledWith(5, 'Unsupported Bruno Deeplink URL');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user