Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,36 +1,27 @@
import React, { memo } from 'react';
import semver from 'semver';
import React, { memo, Fragment } from 'react';

const DEFAULT_VERSION = 'v1.0.0';

/**
* Normalise a raw collection version for display: coerce partials to a full
* major.minor.patch ("1" -> "v1.0.0", "2.1" -> "v2.1.0"), keep a single "v"
* prefix, preserve pre-releases ("1.0.0-beta" -> "v1.0.0-beta"), and fall back to
* the default when the version is unset or unparseable.
*/
const formatVersion = (version) => {
const coerced = semver.coerce(version, { includePrerelease: true });
return coerced ? `v${coerced.version}` : DEFAULT_VERSION;
};

/**
* Read-only display of the collection's current version and a summary of its
* contents (folder + request counts). Presentational and prop-driven so it can be
* reused wherever the collection version needs to be shown.
*/
const CollectionVersionInfo = ({ version, folderCount = 0, requestCount = 0 }) => {
const CollectionVersionInfo = ({ name, version, folderCount = 0, requestCount = 0, environmentCount = 0 }) => {
const folderLabel = folderCount === 1 ? 'Folder' : 'Folders';
const requestLabel = requestCount === 1 ? 'request' : 'requests';

return (
<div className="version-info" data-testid="version-info">
<div className="version-line">
<span className="version-label">Collection Version:</span>{' '}
<span className="version-value" data-testid="version-value">{formatVersion(version)}</span>
<span className="collection-name" data-testid="collection-name">{name}</span>
{version ? (
<span className="version-value" data-testid="version-value">{`version : ${version}`}</span>
Comment thread
sachin-bruno marked this conversation as resolved.
) : null}
</div>
<p className="version-summary" data-testid="version-summary">
{`${folderCount} ${folderLabel} • ${requestCount} ${requestLabel}`}
<span>{`${folderCount} ${folderLabel}`}</span>
<span className="version-dot" aria-hidden="true" />
<span>{`${requestCount} ${requestLabel}`}</span>
{environmentCount === 0 ? (
<Fragment>
<span className="version-dot" aria-hidden="true" />
<span>0 environments</span>
</Fragment>
) : null}
</p>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import '@testing-library/jest-dom';
import React from 'react';
import { render, screen } from '@testing-library/react';
import CollectionVersionInfo from './index';

describe('CollectionVersionInfo', () => {
it('shows the raw collection version with a "version :" prefix and no "v"/semver formatting', () => {
render(<CollectionVersionInfo name="Hotel Booking API" version="1" />);
expect(screen.getByTestId('version-value')).toHaveTextContent('version : 1');
});

it('shows whatever version string the collection has, unchanged', () => {
render(<CollectionVersionInfo name="API" version="2.3-beta" />);
expect(screen.getByTestId('version-value')).toHaveTextContent('version : 2.3-beta');
});

it('omits the version when the collection has none', () => {
render(<CollectionVersionInfo name="API" />);
expect(screen.queryByTestId('version-value')).not.toBeInTheDocument();
});

it('renders folder and request counts and pluralizes them', () => {
const { rerender } = render(<CollectionVersionInfo name="API" folderCount={2} requestCount={5} environmentCount={3} />);
expect(screen.getByTestId('version-summary')).toHaveTextContent('2 Folders');
expect(screen.getByTestId('version-summary')).toHaveTextContent('5 requests');

rerender(<CollectionVersionInfo name="API" folderCount={1} requestCount={1} environmentCount={3} />);
expect(screen.getByTestId('version-summary')).toHaveTextContent('1 Folder');
expect(screen.getByTestId('version-summary')).toHaveTextContent('1 request');
});

it('shows "0 environments" only when there are no environments', () => {
const { rerender } = render(<CollectionVersionInfo name="API" folderCount={0} requestCount={0} environmentCount={0} />);
expect(screen.getByTestId('version-summary')).toHaveTextContent('0 environments');

rerender(<CollectionVersionInfo name="API" folderCount={0} requestCount={0} environmentCount={2} />);
expect(screen.getByTestId('version-summary')).not.toHaveTextContent('environments');
});
Comment thread
sachin-bruno marked this conversation as resolved.
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import React, { useCallback, useEffect, useMemo, useRef, memo } from 'react';
import { Virtuoso } from 'react-virtuoso';
import ColorBadge from 'components/ColorBadge';

// Show at most 5 environments at a glance; the list virtualises and scrolls beyond
// Show at most 6 environments at a glance; the list virtualises and scrolls beyond
// that, so it stays performant even for collections with hundreds of environments
// (only the visible rows are ever in the DOM).
const MAX_VISIBLE_ROWS = 5;
const MAX_VISIBLE_ROWS = 6;

// Fixed row height (px). MUST stay in sync with the `.env-row` height in StyledWrapper.js,
// since it is passed to Virtuoso as `fixedItemHeight`.
const ENV_ROW_HEIGHT = 34;
const ENV_ROW_HEIGHT = 28;

/**
* A selectable, virtualised list of collection environments (checkbox + color dot + name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,57 @@ const StyledWrapper = styled.div`
}

.config-card {
border: 1px solid ${(props) => props.theme.border.border1};
border: 1px solid ${(props) => props.theme.table.border};
border-radius: ${(props) => props.theme.border.radius.md};
overflow: hidden;
width: 100%;

.version-info {
padding: 0.75rem 1rem;
background-color: ${(props) => props.theme.background.mantle};

.version-line {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.text};
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}

.version-label {
.collection-name {
font-weight: 500;
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.text};
min-width: 0;
}

.version-value {
font-weight: 400;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext2};
white-space: nowrap;
}

.version-summary {
display: flex;
align-items: center;
gap: 0.375rem;
margin: 0.25rem 0 0;
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.muted};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext2};
}

.version-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: ${(props) => props.theme.colors.text.subtext0};
flex-shrink: 0;
}
}

.card-divider {
height: 1px;
background-color: ${(props) => props.theme.border.border1};
background-color: ${(props) => props.theme.table.border};
}

.env-section {
Expand All @@ -59,7 +83,7 @@ const StyledWrapper = styled.div`
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.75rem;
margin-bottom: 0.15rem;
}

.env-section-heading {
Expand All @@ -70,18 +94,18 @@ const StyledWrapper = styled.div`
}

.env-section-count {
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.muted};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext2};
white-space: nowrap;
}

.env-section-title {
margin: 0;
font-size: ${(props) => props.theme.font.size.xs};
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: ${(props) => props.theme.colors.text.muted};
color: ${(props) => props.theme.colors.text.subtext2};
}

.env-select-all {
Expand All @@ -94,7 +118,7 @@ const StyledWrapper = styled.div`

.env-select-all-label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.muted};
color: ${(props) => props.theme.colors.text.subtext2};
white-space: nowrap;
}
}
Expand All @@ -105,12 +129,12 @@ const StyledWrapper = styled.div`
gap: 0.5rem;
/* Fixed row height — MUST match ENV_ROW_HEIGHT (Virtuoso fixedItemHeight)
in EnvironmentSelectionList. The inter-row spacing is baked in here. */
height: 34px;
height: 28px;
cursor: pointer;
margin: 0;

.env-name {
font-size: ${(props) => props.theme.font.size.sm};
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.text};
min-width: 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ const GenerateDocumentation = ({ onClose, collectionUid }) => {
handleCancel={onClose}
confirmDisabled={isLoading}
>
<StyledWrapper className="w-[500px]">
<StyledWrapper>
{isLoading ? (
<div className="flex items-center justify-center gap-3 py-8">
<IconLoader2 size={20} className="animate-spin" />
Expand All @@ -215,7 +215,7 @@ const GenerateDocumentation = ({ onClose, collectionUid }) => {
</ul>

<div className="config-card mb-4">
<CollectionVersionInfo version={currentVersion} folderCount={folderCount} requestCount={requestCount} />
<CollectionVersionInfo name={collection.name} version={currentVersion} folderCount={folderCount} requestCount={requestCount} environmentCount={environments.length} />
{environments.length > 0 && (
<Fragment>
<div className="card-divider" />
Expand Down
15 changes: 10 additions & 5 deletions tests/collection/generate-docs/generate-docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ test.describe('Generate Documentation', () => {
await expect(modal).toBeHidden();
});

test('shows the current collection version formatted as a v-prefixed semver', async ({
test('shows the collection name alongside the raw collection version', async ({
pageWithUserData: page
}) => {
const locators = buildCommonLocators(page);
Expand All @@ -176,13 +176,18 @@ test.describe('Generate Documentation', () => {
const modal = locators.generateDocs.modal();
await expect(modal).toBeVisible();

// The fixture's bruno.json version ("1") is normalised for display to "v1.0.0".
await expect(locators.generateDocs.versionInfo()).toContainText('Collection Version:');
await expect(locators.generateDocs.versionValue()).toHaveText('v1.0.0');
await expect(locators.generateDocs.collectionName()).toHaveText(COLLECTION_NAME);

// The version is shown exactly as the collection sets it.
await expect(locators.generateDocs.versionValue()).toHaveText('version : 1');

// The fixture has 2 folders (Zoo, Aviary) and 5 requests (Lion, Bear, Parrot,
// ReqAlpha, ReqBeta), counted recursively across the whole tree.
await expect(locators.generateDocs.versionCounts()).toHaveText('2 Folders • 5 requests');
await expect(locators.generateDocs.versionCounts()).toContainText('2 Folders');
await expect(locators.generateDocs.versionCounts()).toContainText('5 requests');

// This collection has environments, so the "0 environments" hint is not shown.
await expect(locators.generateDocs.versionCounts()).not.toContainText('environments');

await locators.generateDocs.cancelButton().click();
await expect(modal).toBeHidden();
Expand Down
3 changes: 2 additions & 1 deletion tests/utils/page/locators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ export const buildCommonLocators = (page: Page) => ({
heading: () => page.locator('.bruno-modal').getByText('Interactive API Documentation'),
generateButton: () => page.locator('.bruno-modal').getByRole('button', { name: 'Generate', exact: true }),
cancelButton: () => page.locator('.bruno-modal').getByRole('button', { name: 'Cancel', exact: true }),
// Collection version (read-only) display
// Collection name + version (read-only) display
versionInfo: () => page.locator('.bruno-modal').getByTestId('version-info'),
collectionName: () => page.locator('.bruno-modal').getByTestId('collection-name'),
versionValue: () => page.locator('.bruno-modal').getByTestId('version-value'),
versionCounts: () => page.locator('.bruno-modal').getByTestId('version-summary'),
// Environment selection list
Expand Down
Loading