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
Expand Up @@ -33,15 +33,16 @@ export class SearchComponent extends BaseComponent {
readonly clearButton = this.root.getByRole('button', { name: 'Clear search' });
/** Below-desktop Topbar trigger that reveals the search. */
readonly toggleIcon = this.root.getByRole('button', { name: /^search$/i });
readonly folderButton = this.root.getByRole('button', { name: 'Folder', exact: true });
readonly folderMenu = this.root.getByRole('listbox', { name: 'Filter by folder' });
readonly tagFilter = this.root.getByTestId('search-tag-filter');
readonly tagButton = this.root.getByTestId('search-tag-filter-button');
readonly tagMenu = this.root.getByTestId('search-tag-filter-menu');

methodChip(label: string): Locator {
return this.root.getByRole('button', { name: label, exact: true });
}

folderOption(name: string): Locator {
return this.folderMenu.getByRole('button', { name, exact: true });
tagOption(name: string): Locator {
return this.tagMenu.getByRole('button', { name, exact: true });
}

result(text: string): Locator {
Expand Down
2 changes: 2 additions & 0 deletions packages/bruno-api-docs/e2e/pages/request.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class RequestPage extends BasePage {
readonly root = this.page.getByTestId('request-page');
readonly title = this.page.getByTestId('request-title');
readonly description = this.page.getByTestId('request-description');
readonly tagChips = this.page.getByTestId('request-tags-chip');
readonly inheritedTagChips = this.page.getByTestId('request-tags-inherited-chip');

readonly sidebar = new SidebarComponent(this.page);
readonly breadcrumb = new BreadcrumbComponent(this.page, 'request-breadcrumb');
Expand Down
41 changes: 41 additions & 0 deletions packages/bruno-api-docs/e2e/tests/request/request-details.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,44 @@ test.describe('Request page — Details', () => {
});
});
});

test.describe('Request page — Tags section', () => {
test('shows the request tags as chips', async ({ requestPage }) => {
await requestPage.open(['echo json']);
const tags = requestPage.section('Tags');
await expect(tags).toBeVisible();
await expect(requestPage.tagChips).toHaveCount(2);
await expect(tags).toContainText('echo');
await expect(tags).toContainText('smoke');
});

test('renders no tags section for an untagged request', async ({ requestPage }) => {
await requestPage.open(['patch user']);
await expect(requestPage.section('Tags')).toHaveCount(0);
});

test('shows tags inherited from the whole folder chain with a count badge', async ({ requestPage }) => {
await requestPage.open(['billing', 'customers', 'Get Customers - Filter by Status']);
const tags = requestPage.section('Tags');
await expect(tags).toBeVisible();
await expect(tags).toContainText('2 tags inherited');
await expect(requestPage.inheritedTagChips).toHaveCount(2);
await expect(requestPage.inheritedTagChips.first()).toContainText('billing');
await expect(requestPage.inheritedTagChips.last()).toContainText('customers');
});

test('shows own and inherited tags side by side', async ({ requestPage }) => {
await requestPage.open(['billing', 'customers', 'Get All Customers']);
await expect(requestPage.tagChips).toHaveCount(1);
await expect(requestPage.tagChips.first()).toContainText('smoke');
await expect(requestPage.inheritedTagChips).toHaveCount(2);
});

test('a tag owned and inherited shows once, as own', async ({ requestPage }) => {
await requestPage.open(['billing', 'subscriptions', 'Get All Subscriptions']);
await expect(requestPage.tagChips).toHaveCount(1);
await expect(requestPage.tagChips.first()).toContainText('billing');
await expect(requestPage.inheritedTagChips).toHaveCount(0);
await expect(requestPage.section('Tags')).not.toContainText('inherited');
});
});
110 changes: 99 additions & 11 deletions packages/bruno-api-docs/e2e/tests/search/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,41 +371,129 @@ test.describe('Search palette', () => {
expect((opt?.y ?? 0) + (opt?.height ?? 0)).toBeLessThanOrEqual((box?.y ?? 0) + (box?.height ?? 0) + 1);
});

test('folder dropdown closes on an outside click', async ({ page, search }) => {
test('tag dropdown closes on an outside click', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.folderButton.click();
await expect(search.folderMenu).toBeVisible();
await search.tagButton.click();
await expect(search.tagMenu).toBeVisible();

await search.field.click(); // click outside the dropdown
await expect(search.folderMenu).toHaveCount(0);
await expect(search.tagMenu).toHaveCount(0);
});

test('folder filter scopes results to the chosen folder', async ({ page, search }) => {
test('tag filter narrows results to records carrying the tag', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.folderButton.click();
await search.folderOption('Authentication').click();
await search.tagButton.click();
await search.tagOption('auth').click();

await expect(search.panel).toContainText('Login');
await expect(search.panel).toContainText('Refresh Token');
await expect(search.panel).not.toContainText('Create Booking');
});

test('the filtered folder appears as its own result, ahead of its contents', async ({ page, search }) => {
test('multiple tags narrow to records carrying all of them', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.folderButton.click();
await search.folderOption('Bookings').click();
await search.tagButton.click();
await search.tagOption('auth').click();
await search.tagOption('smoke').click();

await expect(search.panel).toContainText('Login');
await expect(search.panel).not.toContainText('Refresh Token');
await expect(search.panel).not.toContainText('Health Check');
});

test('folder tags are inherited: the folder matches itself and everything beneath it', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.tagButton.click();
await search.tagOption('bookings').click();

await expect(search.folderResults).toHaveCount(3);
await expect(search.folderResults.nth(0)).toContainText('Bookings');
await expect(search.results.first()).toContainText('Bookings');
await expect(search.panel).toContainText('Create Booking');
await expect(search.panel).not.toContainText('Login');
});

test('tag filter combines with method chips', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.tagButton.click();
await search.tagOption('bookings').click();
await search.field.click();
await search.methodChip('DEL').click();

await expect(search.results).toHaveCount(1);
await expect(search.results.first()).toContainText('Cancel Booking');
});

test('tag filter narrows typed-query results too', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();
await search.field.fill('payment');

// The Payments requests match the query and inherit "bookings" from their
// ancestor folder; Login matches neither.
await search.tagButton.click();
await search.tagOption('bookings').click();

await expect(search.panel).toContainText('Charge Payment');
await expect(search.panel).toContainText('Refund Payment');
await expect(search.panel).not.toContainText('Login');
});

test('"Clear all" resets the method and tag filters together', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.tagButton.click();
await search.tagOption('auth').click();
await search.field.click(); // close the menu
await search.methodChip('POST').click();

await search.root.getByRole('button', { name: 'Clear all' }).click();

// With no query and no filters the palette returns to its initial prompt.
await expect(search.panel).toContainText('Search the collection');
await expect(search.tagButton).toHaveText(/Tags/);
});

test('a graphql request is searchable and filterable by its tag', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto('/'); // the testbench holds the tagged GraphQL request
await search.field.click();
await search.field.fill('graphql details');

await expect(search.panel).toContainText('GraphQL Details');

await search.field.clear();
await search.tagButton.click();
await search.tagOption('catalog').click();

await expect(search.panel).toContainText('GraphQL Details');
await expect(search.panel).not.toContainText('Order Service');
});

test('a collection without tags offers no tag filter', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto('/?fixture=vars');
await search.field.click();

await expect(search.filters).toBeVisible();
await expect(search.tagFilter).toHaveCount(0);
});

test('tablet: the toggle reveals a panel that stays within the viewport', async ({ page, search }) => {
Expand Down
9 changes: 9 additions & 0 deletions packages/bruno-api-docs/src/assets/icons/TagIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { baseIconProps } from './baseIconProps';

export const TagIcon: React.FC = () => (
<svg {...baseIconProps} width={16} height={16}>
<path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" />
<circle cx="7.5" cy="7.5" r=".5" fill="currentColor" />
</svg>
);
1 change: 1 addition & 0 deletions packages/bruno-api-docs/src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from './DockBottomIcon';
export * from './DockModalIcon';
export * from './SidebarToggleIcon';
export * from './SettingsIcon';
export * from './TagIcon';
export * from './TrashIcon';
export * from './ExampleIcon';
export * from './DotIcon';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { getByTestId, queryByTestId } from '@/test-utils/dom';
const makeData = (overrides: Partial<RequestPageData> = {}): RequestPageData =>
({
name: 'Get Users',
tags: [],
inheritedTags: [],
url: '{{baseUrl}}/users',
descHtml: '',
pathParams: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { PropertyTable } from '@/components/PropertyTable/PropertyTable';
import { InheritedAuthBadge } from '@/components/InheritedAuthBadge/InheritedAuthBadge';
import { ExecutionContext } from '@/components/ExecutionContext/ExecutionContext';
import { CodeSnippetTabs } from '@/components/CodeSnippetTabs/CodeSnippetTabs';
import { Tags } from '@/components/Tags/Tags';
import type { HttpRequestBody, HttpRequestBodyVariant } from '@opencollection/types/requests/http';
import type { RequestPageData } from '@/hooks/useRequestPageData';
import { StyledWrapper } from './StyledWrapper';
Expand Down Expand Up @@ -52,6 +53,8 @@ export const RequestPageLayout: React.FC<RequestPageLayoutProps> = ({
}) => {
const {
name,
tags,
inheritedTags,
url,
descHtml,
pathParams,
Expand Down Expand Up @@ -165,6 +168,21 @@ export const RequestPageLayout: React.FC<RequestPageLayoutProps> = ({
auth={effectiveAuth}
/>
</Section>
{(tags.length > 0 || inheritedTags.length > 0) && (
<Section
label="Tags"
testId="request-section-tags"
hideFromNav
labelClassName="section-label-lower"
badge={
inheritedTags.length > 0 ? (
<ContentTypeBadge label={inheritedCountLabel(inheritedTags.length, 'tag')} />
) : undefined
}
>
<Tags tags={tags} inheritedTags={inheritedTags} testId="request-tags" />
</Section>
)}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export const StyledWrapper = styled.div`

.request-col-right {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1.5rem;
position: sticky;
top: 1.25rem;
align-self: start;
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading