Skip to content
Merged
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
13 changes: 13 additions & 0 deletions .changeset/many-buckets-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@usebruno/api-docs": patch
---

fix(docs): parity UI bugs 2
16: On the gRPC request page, the URL bar now stays at the top while you scroll, like the other request pages.
20: The "Show vars" switch now shows a tooltip.
22: In the script editor's search box, the button tooltips no longer get cut off or flicker.
27: In example cards, the scrollbar now only shows when you hover over the content.
28: Letters like g, y and p are no longer cut off at the bottom in the collection name, variable card, environments page, table keys and search results.
30: In search results, method names like POST and PATCH are shown in full instead of being cut off with "...".
35: Tooltips now match the light and dark theme instead of being the opposite colour.
36: Description tooltips now keep the line breaks and blank lines written in the Bruno app.
12 changes: 12 additions & 0 deletions packages/bruno-api-docs/e2e/components/base.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ export abstract class BaseComponent {
this.root = root ?? page.locator(':root');
}

async hidesVerticalOverflow(target: Locator): Promise<boolean> {
return target.evaluate((el) => getComputedStyle(el).overflowY !== 'visible');
}

async isContentWiderThanBox(target: Locator): Promise<boolean> {
return target.evaluate((el) => el.scrollWidth > el.clientWidth + 1);
}

async getScrollbarColor(target: Locator): Promise<string> {
return target.evaluate((el) => getComputedStyle(el).scrollbarColor);
}

/** Press the pointer on a resize handle; the grab point is kept for later moves. */
protected async grabHandle(handle: Locator): Promise<void> {
const box = await handle.boundingBox();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { BaseComponent } from '../base.component';
export class CodeEditorComponent extends BaseComponent {
readonly copyButton: Locator;
readonly suggestions: Locator;
readonly searchBox: Locator;
readonly contextViewHover: Locator;
private readonly surface: Locator;
private readonly focused: Locator;
private readonly lines: Locator;
Expand All @@ -21,6 +23,8 @@ export class CodeEditorComponent extends BaseComponent {
this.focused = this.root.locator('.monaco-editor.focused');
this.lines = this.root.locator('.view-lines');
this.suggestions = page.locator('.suggest-widget.visible');
this.searchBox = this.root.locator('.find-widget.visible');
this.contextViewHover = page.getByTestId(`${testId}-context-view-host`).locator('.monaco-hover');
this.ready = page.locator(`[data-testid="${testId}"][data-editor-ready="true"]`);
}

Expand All @@ -38,4 +42,14 @@ export class CodeEditorComponent extends BaseComponent {
await this.page.keyboard.type(text, { delay: 30 });
await this.page.keyboard.press('Control+Space');
}

async openSearchBox(): Promise<void> {
await this.focus();
await this.page.keyboard.press('Control+f');
await this.searchBox.waitFor({ state: 'visible' });
}

searchBoxButton(label: RegExp): Locator {
return this.searchBox.getByLabel(label);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BaseComponent } from '../base.component';
*/
export class EnvSwitcherComponent extends BaseComponent {
readonly showVarsToggle = this.page.getByTestId('show-vars-toggle');
readonly showVarsTooltip = this.page.getByTestId('show-vars-toggle-tooltip');
readonly trigger: Locator;
readonly menu: Locator;
readonly surface: Locator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BaseComponent } from '../base.component';

export class ThemeToggleComponent extends BaseComponent {
readonly button = this.page.getByTestId('theme-toggle');
readonly tooltip = this.page.getByTestId('theme-toggle-tooltip');

async toggle(): Promise<void> {
await this.button.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ export class SearchComponent extends BaseComponent {
result(text: string): Locator {
return this.results.filter({ hasText: text });
}

resultMethod(text: string): Locator {
return this.result(text).getByTestId('search-result-method');
}
}
17 changes: 17 additions & 0 deletions packages/bruno-api-docs/e2e/components/tooltip.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,21 @@ export class TooltipComponent extends BaseComponent {
async hoverCell(index: number): Promise<void> {
await this.truncatableCells.nth(index).hover();
}

cellWithText(text: string): Locator {
return this.truncatableCells.filter({ hasText: text });
}

async popupColorsAgainstTheme(): Promise<{ popup: string[]; theme: string[] }> {
return this.popup.evaluate((el) => {
const probe = document.createElement('div');
probe.style.background = 'var(--oc-background-surface0)';
probe.style.color = 'var(--oc-text)';
document.body.appendChild(probe);
const theme = [getComputedStyle(probe).backgroundColor, getComputedStyle(probe).color];
probe.remove();
const style = getComputedStyle(el);
return { popup: [style.backgroundColor, style.color], theme };
});
}
}
2 changes: 2 additions & 0 deletions packages/bruno-api-docs/e2e/pages/grpc-request.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class GrpcRequestPage extends BasePage {
readonly root: Locator = this.page.getByTestId('grpc-request-page');
readonly title: Locator = this.page.getByTestId('grpc-request-title');
readonly description: Locator = this.page.getByTestId('grpc-request-description');
readonly urlSticky: Locator = this.page.getByTestId('grpc-request-url-sticky');

readonly protoFileSection: Locator = this.page.getByTestId('grpc-request-section-proto-file');
readonly protoFile: Locator = this.page.getByTestId('grpc-request-proto-file');
Expand All @@ -38,6 +39,7 @@ export class GrpcRequestPage extends BasePage {
readonly codeSnippet: Locator = this.snippet.code;

readonly executionContextSection: Locator = this.page.getByTestId('grpc-request-section-execution-context');
readonly executionContextToggle: Locator = this.page.getByTestId('grpc-request-section-execution-context-toggle');
readonly executionContextEmpty: Locator = this.page.getByTestId('grpc-request-execution-context-empty');

async open(path: string[]): Promise<void> {
Expand Down
30 changes: 30 additions & 0 deletions packages/bruno-api-docs/e2e/tests/app/storage-keys.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { test, expect } from '../../playwright';

const USERS_BODY = '{"data":[{"id":1,"name":"Alice"}]}';

test.describe('Browser storage keys', () => {
test('everything the app remembers is stored under the oc-docs prefix', async ({
page,
grpcRequestPage,
playground,
responsePane
}) => {
await grpcRequestPage.open(['Realtime', 'Order Service']);
await grpcRequestPage.executionContextToggle.click();

await responsePane.mockUsersResponse(USERS_BODY);
await playground.open('bottom');
await playground.openRequest('get users');
await playground.selectTab('headers');
await responsePane.send();
await responsePane.switchToTab('headers');

const keys = await page.evaluate(() => [...Object.keys(sessionStorage), ...Object.keys(localStorage)]);
expect(keys).toEqual(expect.arrayContaining([
'oc-docs:playgroundRequestTab',
'oc-docs:playgroundResponseTab',
'oc-docs:section-grpc-request-execution-context'
]));
expect(keys.filter((key) => !key.startsWith('oc-docs'))).toEqual([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test.describe('Environments page', () => {

test('opens from the sidebar and shows a tab per environment', async ({ environmentsPage }) => {
await expect(environmentsPage.title).toHaveText('Environments');
await expect(environmentsPage.tabs).toHaveCount(2);
await expect(environmentsPage.tabs).toHaveCount(4);
await expect(environmentsPage.tab('Local')).toBeVisible();
await expect(environmentsPage.tab('Prod')).toBeVisible();
});
Expand Down
16 changes: 16 additions & 0 deletions packages/bruno-api-docs/e2e/tests/layout/page-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ test.describe('Page header', () => {
expect(box.y).toBeLessThanOrEqual(1);
});

test('shows the full height of the brand name, descenders included', async ({ page, pageHeader }) => {
await page.setViewportSize(DESKTOP);
await page.goto('/');

expect(await pageHeader.hidesVerticalOverflow(pageHeader.brandName)).toBe(false);
});

test('explains the Show vars switch in a tooltip on hover', async ({ page, envSwitcher }) => {
await page.setViewportSize(DESKTOP);
await page.goto('/');

await envSwitcher.showVarsToggle.hover();

await expect(envSwitcher.showVarsTooltip).toHaveText('Show resolved variable values');
});

test('shows the initials avatar derived from the collection name', async ({ page, pageHeader }) => {
await page.setViewportSize(DESKTOP);
await page.goto('/');
Expand Down
6 changes: 3 additions & 3 deletions packages/bruno-api-docs/e2e/tests/overview/overview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ test.describe('Collection Overview', () => {
});
});

test('shows three stat cards with the request (61), folder (14) and environment (2) counts', async ({ overviewPage }) => {
test('shows three stat cards with the request (62), folder (14) and environment (4) counts', async ({ overviewPage }) => {
await expect(overviewPage.stats.cards).toHaveCount(3);
await expect(overviewPage.stats.valueFor('Requests')).toHaveText('61');
await expect(overviewPage.stats.valueFor('Requests')).toHaveText('62');
await expect(overviewPage.stats.valueFor('Folders')).toHaveText('14');
await expect(overviewPage.stats.valueFor('Environments')).toHaveText('2');
await expect(overviewPage.stats.valueFor('Environments')).toHaveText('4');
});

test('renders the collection documentation under the "Overview" section', async ({ overviewPage }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { test, expect } from '../../playwright';

test.describe('Playground code editor: find widget tooltips', () => {
test.beforeEach(async ({ playground }) => {
await playground.open('bottom');
await playground.openRequest('get users');
await playground.selectTab('scripts');
await playground.preRequestScriptEditor.openSearchBox();
});

for (const label of [/^Find in Selection/, /^Close/, /^Previous Match/]) {
test(`the "${label.source.slice(1)}" tooltip renders outside the editor, on one line, above its button`, async ({ playground }) => {
const editor = playground.preRequestScriptEditor;
const button = editor.searchBoxButton(label);

await button.hover();
await expect(editor.contextViewHover).toBeVisible();

const buttonBox = await button.boundingBox();
const hoverBox = await editor.contextViewHover.boundingBox();
expect(hoverBox!.y + hoverBox!.height).toBeLessThanOrEqual(buttonBox!.y);
expect(hoverBox!.height).toBeLessThan(buttonBox!.height * 2);
});
}
});
14 changes: 14 additions & 0 deletions packages/bruno-api-docs/e2e/tests/request/grpc-request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ test.describe('Request page — gRPC requests', () => {
await expect(grpcRequestPage.urlBar.tryButton).toHaveCount(0);
});

test('keeps the URL bar pinned below the header while the page scrolls', async ({ page, grpcRequestPage }) => {
await page.setViewportSize({ width: 1280, height: 600 });
await grpcRequestPage.open([REALTIME, 'Order Service']);
const before = await grpcRequestPage.urlSticky.boundingBox();

await grpcRequestPage.urlSticky.hover();
await page.mouse.wheel(0, 900);

await expect(grpcRequestPage.title).not.toBeInViewport();
await expect(grpcRequestPage.urlSticky).toBeInViewport();
const after = await grpcRequestPage.urlSticky.boundingBox();
expect(after!.y).toBeLessThan(before!.y);
});

test('marks the grpcurl command plaintext for an unencrypted environment', async ({ grpcRequestPage }) => {
await grpcRequestPage.open([REALTIME, 'Order Service']);

Expand Down
11 changes: 11 additions & 0 deletions packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ test.describe('Request page — Examples', () => {
await expect(examples.responseBody(OK_EXAMPLE)).toContainText('john.smith@example.com');
});

test('keeps the response scrollbar hidden until the pane is hovered', async ({ page, requestPage }) => {
const { examples } = requestPage;
const body = examples.responseBody(OK_EXAMPLE);
await page.mouse.move(0, 0);

expect(await examples.getScrollbarColor(body)).toBe('rgba(0, 0, 0, 0) rgba(0, 0, 0, 0)');

await body.hover();
expect(await examples.getScrollbarColor(body)).not.toBe('rgba(0, 0, 0, 0) rgba(0, 0, 0, 0)');
});

test('switches to the Headers tab to reveal the response headers', async ({ requestPage }) => {
const { examples } = requestPage;
await examples.selectResponseTab(OK_EXAMPLE, 'headers');
Expand Down
16 changes: 16 additions & 0 deletions packages/bruno-api-docs/e2e/tests/search/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ test.describe('Search palette', () => {
await expect(search.panel).toContainText('Login');
});

test('shows a five-letter method in full and shortens a longer one to three letters, never with an ellipsis', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
await search.field.click();

await search.field.fill('confirm booking');
const patch = search.resultMethod('Confirm Booking');
await expect(patch).toHaveText('PATCH');
expect(await search.isContentWiderThanBox(patch)).toBe(false);

await search.field.fill('cancel booking');
const del = search.resultMethod('Cancel Booking');
await expect(del).toHaveText('DEL');
expect(await search.isContentWiderThanBox(del)).toBe(false);
});

test('corrects a typo (adjacent letter swap) to the intended request', async ({ page, search }) => {
await page.setViewportSize(DESKTOP);
await page.goto(FIXTURE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ test.describe('Theme switcher', () => {
await expect(themeToggle.button).toHaveAccessibleName('Switch to dark theme');
});

test('names the action in a tooltip on hover', async ({ themeToggle }) => {
await themeToggle.button.hover();

await expect(themeToggle.tooltip).toHaveText('Switch to dark theme');
});

test('switches the whole app to dark mode when the toggle is clicked', async ({ page, themeToggle }) => {
await themeToggle.toggle();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,37 @@ test.describe('Truncation tooltips', () => {
await expect(tooltip.popup).toHaveCount(0);
});
});

test.describe('Description tooltips', () => {
const DESCRIBED_REQUEST = '/?fixture=descriptions#/described-request';
const MULTI_PARAGRAPH = 'Identifies this request across every service that handles it, from the gateway to the database.\n\nSend a fresh value per call so retries can be told apart in the logs.';

test('keeps the line breaks of a multi-paragraph description', async ({ page, tooltip }) => {
await page.goto(DESCRIBED_REQUEST);

await tooltip.cellWithText('Identifies this request').hover();

await expect(tooltip.popup).toBeVisible();
expect(await tooltip.popup.innerText()).toBe(MULTI_PARAGRAPH);
});
});

test.describe('Tooltip theme', () => {
test.use({ colorScheme: 'light' });

test('uses the theme surface and text colors in light and dark mode', async ({ page, tooltip, themeToggle }) => {
await page.goto('/?fixture=descriptions#/described-request');

await tooltip.cellWithText('Identifies this request').hover();
await expect(tooltip.popup).toBeVisible();
const light = await tooltip.popupColorsAgainstTheme();
expect(light.popup).toEqual(light.theme);

await themeToggle.toggle();
await tooltip.cellWithText('Identifies this request').hover();
await expect(tooltip.popup).toBeVisible();
const dark = await tooltip.popupColorsAgainstTheme();
expect(dark.popup).toEqual(dark.theme);
expect(dark.popup[0]).not.toBe(light.popup[0]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ test.describe('Variable hover card', () => {
await expect(variableCard.value).toHaveText('https://api.dev.example.com');
});

test('shows the full height of the variable name, descenders included', async ({ requestPage }) => {
const { variableCard } = requestPage;
await variableCard.hoverToken('apiVersion');

await expect(variableCard.name).toBeVisible();
expect(await variableCard.hidesVerticalOverflow(variableCard.name)).toBe(false);
});

test('badges collection, folder and request scopes correctly', async ({ page, requestPage }) => {
const { variableCard } = requestPage;
await variableCard.hoverToken('apiVersion');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface DescriptionProps {
export const Description: React.FC<DescriptionProps> = ({ text, className }) => {
const trimmed = text?.trim();
return trimmed ? (
<StyledWrapper text={trimmed} className={['description', className].filter(Boolean).join(' ')} />
<StyledWrapper text={trimmed} multilineTooltip className={['description', className].filter(Boolean).join(' ')} />
) : null;
};

Expand Down
Loading
Loading