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
12 changes: 12 additions & 0 deletions src/components/ApiDoc/ApiDoc.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ export const HiddenTitle: Story = {
},
};

/**
* Adds a Modern / Classic switch in the top right corner. The choice is
* remembered in localStorage; `configuration.layout` is the starting layout.
*/
export const LayoutSwitch: Story = {
args: {
url: 'ecocor.yaml',
layoutSwitch: true,
configuration: { layout: 'modern' },
},
};

export const Dracor: Story = {
args: {
url: 'https://dracor.org/api/v1/openapi.yaml',
Expand Down
166 changes: 163 additions & 3 deletions src/components/ApiDoc/ApiDoc.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { act } from '@testing-library/react';
import { act, fireEvent, screen } from '@testing-library/react';
import { vi } from 'vitest';
import { renderWithProviders } from '../../testHelpers';

vi.mock('@scalar/api-reference-react', () => ({
ApiReferenceReact: ({
configuration,
}: {
configuration: { url: string };
}) => <div data-testid="scalar-stub">{configuration.url}</div>,
configuration: {
url: string;
layout?: string;
showSidebar?: boolean;
mcp?: { disabled?: boolean };
hideClientButton?: boolean;
customCss?: string;
};
}) => (
<div
data-testid="scalar-stub"
data-layout={configuration.layout}
data-sidebar={String(configuration.showSidebar)}
data-mcp-disabled={String(configuration.mcp?.disabled)}
data-hide-client={String(configuration.hideClientButton)}
data-css={configuration.customCss}
>
{configuration.url}
</div>
),
}));

import ApiDoc from './ApiDoc';
Expand All @@ -18,4 +36,146 @@ describe('ApiDoc', () => {
renderWithProviders('/', <ApiDoc url="ecocor.yml" title="EcoCor API" />);
});
});

test('hides the MCP and API client buttons', async () => {
renderWithProviders('/', <ApiDoc url="ecocor.yml" />);
const stub = await screen.findByTestId('scalar-stub');
expect(stub).toHaveAttribute('data-mcp-disabled', 'true');
expect(stub).toHaveAttribute('data-hide-client', 'true');
});

test('hides the "Powered by Scalar" link on request only', async () => {
const { unmount } = renderWithProviders('/', <ApiDoc url="ecocor.yml" />);
expect(
(await screen.findByTestId('scalar-stub')).getAttribute('data-css') ?? ''
).not.toContain('scalar.com');
unmount();
renderWithProviders(
'/',
<ApiDoc
url="ecocor.yml"
hidePoweredBy
configuration={{ customCss: '.mine {}' }}
/>
);
const css =
(await screen.findByTestId('scalar-stub')).getAttribute('data-css') ?? '';
expect(css).toContain('.mine {}');
expect(css).toContain('a[href^="https://scalar.com/"]');
});

describe('showSidebar', () => {
test('is off by default, in either layout', async () => {
renderWithProviders('/', <ApiDoc url="ecocor.yml" layoutSwitch />);
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-sidebar',
'false'
);
fireEvent.click(screen.getByRole('radio', { name: 'Modern' }));
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-sidebar',
'false'
);
localStorage.clear();
});

test('can be enabled for one layout only', async () => {
renderWithProviders(
'/',
<ApiDoc
url="ecocor.yml"
layoutSwitch={{ modern: { showSidebar: true } }}
/>
);
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-sidebar',
'false'
);
fireEvent.click(screen.getByRole('radio', { name: 'Modern' }));
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-sidebar',
'true'
);
localStorage.clear();
});

test('can be enabled', async () => {
renderWithProviders(
'/',
<ApiDoc url="ecocor.yml" configuration={{ showSidebar: true }} />
);
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-sidebar',
'true'
);
});
});

describe('layoutSwitch', () => {
afterEach(() => localStorage.clear());

test('is not rendered by default', async () => {
renderWithProviders('/', <ApiDoc url="ecocor.yml" />);
await screen.findByTestId('scalar-stub');
expect(screen.queryByRole('radiogroup')).not.toBeInTheDocument();
});

test('starts from configuration.layout', async () => {
renderWithProviders(
'/',
<ApiDoc
url="ecocor.yml"
layoutSwitch
configuration={{ layout: 'modern' }}
/>
);
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-layout',
'modern'
);
expect(screen.getByRole('radio', { name: 'Modern' })).toBeChecked();
});

test('switches layout and remembers the choice', async () => {
renderWithProviders('/', <ApiDoc url="ecocor.yml" layoutSwitch />);
fireEvent.click(await screen.findByRole('radio', { name: 'Modern' }));
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-layout',
'modern'
);
expect(localStorage.getItem('dracor.apiDocLayout')).toBe('modern');
});

test('restores a stored layout under a custom key', async () => {
localStorage.setItem('my.key', 'classic');
renderWithProviders(
'/',
<ApiDoc
url="ecocor.yml"
layoutSwitch={{ storageKey: 'my.key' }}
configuration={{ layout: 'modern' }}
/>
);
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-layout',
'classic'
);
});

test('ignores an unknown stored value', async () => {
localStorage.setItem('dracor.apiDocLayout', 'bogus');
renderWithProviders(
'/',
<ApiDoc
url="ecocor.yml"
layoutSwitch
configuration={{ layout: 'modern' }}
/>
);
expect(await screen.findByTestId('scalar-stub')).toHaveAttribute(
'data-layout',
'modern'
);
});
});
});
Loading
Loading