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
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import type { Locator } from '@playwright/test';
import { BaseComponent } from '../base.component';

export class EnvEditorComponent extends BaseComponent {
readonly cards = this.page.getByTestId('env-var-cards');
readonly cardItems = this.cards.locator('.env-card');
readonly nameInputs = this.cards.getByPlaceholder('Name');
readonly valueInputs = this.cards.getByPlaceholder('Value');

/** The per-variable enable/disable checkbox, addressed by the variable name via its aria-label. */
enableToggle(name: string): Locator {
return this.cards.getByRole('checkbox', { name: `Enable ${name}` });
}

/** The card that owns the named variable, matched via its enable checkbox. */
cardFor(name: string): Locator {
return this.cardItems.filter({ has: this.page.getByRole('checkbox', { name: `Enable ${name}` }) });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ export class KeyValueTableComponent extends BaseComponent {
this.cellErrors = page.getByTestId(`${testId}-error`);
this.autocomplete = page.getByTestId('variable-autocomplete');
}

/** The per-row enable/disable checkbox, addressed by the row name via its aria-label. */
enableToggle(name: string): Locator {
return this.table.getByRole('checkbox', { name: `Enable ${name}` });
}
}
20 changes: 20 additions & 0 deletions packages/oc-docs/e2e/tests/playground/env-add-variable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,24 @@ test.describe('Environment variables — adding rows (card view)', () => {
await expect(envEditor.nameInputs.nth(before - 1)).toHaveValue('newVariable');
await expect(envEditor.valueInputs.nth(before - 1)).toHaveValue('newValue');
});

test('toggling a variable checkbox enables/disables its card', async ({ playground, envEditor }) => {
await playground.open('inline');
await playground.openEnvironments();

const toggle = envEditor.enableToggle('host');
const card = envEditor.cardFor('host');
const disabled = /(^|\s)disabled(\s|$)/;

await expect(toggle).toBeChecked();
await expect(card).not.toHaveClass(disabled);

await toggle.uncheck();
await expect(toggle).not.toBeChecked();
await expect(card).toHaveClass(disabled);

await toggle.check();
await expect(toggle).toBeChecked();
await expect(card).not.toHaveClass(disabled);
});
});
15 changes: 15 additions & 0 deletions packages/oc-docs/e2e/tests/playground/keyvalue-table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,19 @@ test.describe('KeyValueTable — tooltips & mobile scroll', () => {
await expect(error).toBeVisible();
await expect(error).toHaveAttribute('aria-label', 'Header name cannot contain spaces or newlines');
});

test('a named row can be enabled and disabled via its checkbox', async ({ playground }) => {
const { keyValueTable } = playground;
// The trailing blank row has no checkbox; naming a row promotes it to a real row with one.
await keyValueTable.nameInputs.last().fill('X-Custom');

const toggle = keyValueTable.enableToggle('X-Custom');
await expect(toggle).toBeChecked();

await toggle.uncheck();
await expect(toggle).not.toBeChecked();

await toggle.check();
await expect(toggle).toBeChecked();
});
});
21 changes: 21 additions & 0 deletions packages/oc-docs/src/assets/icons/CheckIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { baseIconProps } from './baseIconProps';

interface CheckIconProps {
className?: string;
width?: number;
height?: number;
}

export const CheckIcon: React.FC<CheckIconProps> = ({ className, width=24, height=24 }) => (
<svg {...baseIconProps} className={className} width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<path
fill="none"
stroke="currentColor"
strokeWidth="1.67"
strokeLinecap="round"
strokeLinejoin="round"
d="M3 6.2 5 8.4 9 3.8"
/>
</svg>
);
1 change: 1 addition & 0 deletions packages/oc-docs/src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export * from './SidebarToggleIcon';
export * from './SettingsIcon';
export * from './TrashIcon';
export * from './ExampleIcon';
export * from './CheckIcon';
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ describe('SidebarNavLink', () => {

it('indents by level via margin (chevron + gap step of 19px) with a uniform 8px inner pad', () => {
const html = renderToStaticMarkup(<SidebarNavLink label="Nested" level={2} />);
// level*19 + 4 = 42px -> 42/16 = 2.625rem margin; the 8px (0.5rem) pad is uniform
// level*19 = 38px -> 38/16 = 2.375rem margin; the 8px (0.5rem) pad is uniform
// across folders and leaves so glyphs line up under their parent.
expect(html).toContain('margin-left:2.625rem');
expect(html).toContain('margin-left:2.375rem');
expect(html).toContain('padding-left:0.5rem');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const StyledWrapper = styled.span`
flex: none;
border-radius: var(--oc-radius);
background: linear-gradient(135deg, #d37f17 0%, #dc9741 100%);
color: #fff;
color: var(--oc-background-base);
font-family: var(--font-mono);
font-size: var(--oc-font-size-xs);
font-weight: 700;
Expand Down
21 changes: 0 additions & 21 deletions packages/oc-docs/src/components/KeyValueTable/KeyValueTable.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
.key-value-table .checkbox-slot {
flex: none;
display: inline-flex;
align-items: center;
width: 1rem;
height: 1rem;
}
Expand Down Expand Up @@ -140,26 +139,6 @@
vertical-align: middle;
}

.key-value-table .checkbox-input {
appearance: none;
-webkit-appearance: none;
margin: 0;
width: 1rem;
height: 1rem;
border: 0.0625rem solid var(--oc-table-border);
border-radius: 0.1875rem;
background-color: transparent;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}

.key-value-table .checkbox-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%2012%2012'%3E%3Cpath%20fill='none'%20stroke='%23fff'%20stroke-width='1.67'%20stroke-linecap='round'%20stroke-linejoin='round'%20d='M3%206.2%205%208.4%209%203.8'/%3E%3C/svg%3E");
}

.key-value-table .text-input {
width: 100%;
border: 0.0625rem solid transparent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { WarningIcon } from '../../assets/icons';
import HighlightedInput from '../HighlightedInput/HighlightedInput';
import { SecretValue } from '../../ui/SecretValue/SecretValue';
import './KeyValueTable.css';
import Checkbox from '../../ui/Checkbox/Checkbox';

export interface KeyValueRow {
id: string;
Expand Down Expand Up @@ -179,11 +180,9 @@ const KeyValueTable: React.FC<KeyValueTableProps> = ({
{showEnabled && (
<span className="checkbox-slot">
{!isLastEmptyRow && (
<input
type="checkbox"
className="checkbox-input"
<Checkbox
checked={row.enabled}
aria-label={row.name ? `Enable ${row.name}` : 'Enable row'}
ariaLabel={row.name ? `Enable ${row.name}` : 'Enable row'}
onChange={(e) => updateField(index, 'enabled', e.target.checked)}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ describe('HeadersTab', () => {
expect(values).toContain('text/html');
});

it('renders the default title and a provided description', () => {
it('renders a provided title and description', () => {
const root = useRenderToDom(
<HeadersTab headers={[]} onHeadersChange={noop} description="Request headers sent with the call" />
<HeadersTab
headers={[]}
onHeadersChange={noop}
title="Headers"
description="Request headers sent with the call"
/>
);
expect(query(root, '.title').text.trim()).toBe('Headers');
expect(query(root, '.description').text.trim()).toBe('Request headers sent with the call');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ describe('ScriptsTab', () => {
expect(query(root, '[data-testid="scripts-tabs-tab-post-response"]').text.trim()).toBe('Post response');
});

it('renders the default title and the Tests section', () => {
it('renders the Tests section by default', () => {
const root = useRenderToDom(<ScriptsTab scripts={{}} onScriptChange={noop} />);
expect(query(root, '.title').text.trim()).toBe('Scripts');
expect(query(root, '.label').text.trim()).toBe('Tests');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { cx } from '../../../../../../utils/cx';
import { toDataType } from '../../../../../../utils/variableDataType';
import { VariableTypeControl } from '../../Common/VariableTypeControl/VariableTypeControl';
import { StyledWrapper } from './StyledWrapper';
import Checkbox from '../../../../../../ui/Checkbox/Checkbox';

interface EnvVarCardsProps {
rows: KeyValueRow[];
Expand Down Expand Up @@ -38,11 +39,10 @@ const EnvVarCards: React.FC<EnvVarCardsProps> = ({
return (
<div key={row.id} className={cx('env-card', { disabled: !row.enabled })}>
{!isBlankRow && (
<input
type="checkbox"
<Checkbox
className="enabled"
checked={row.enabled}
aria-label={row.name ? `Enable ${row.name}` : 'Enable variable'}
ariaLabel={row.name ? `Enable ${row.name}` : 'Enable variable'}
onChange={(e) => updateRow(index, { enabled: e.target.checked })}
/>
)}
Expand All @@ -69,7 +69,14 @@ const EnvVarCards: React.FC<EnvVarCardsProps> = ({
</div>
<div className="value">
{row.secret ? (
<SecretValue value={row.value} placeholder="Value" editByDefault={secretEditByDefault} multiline={editableDataType} onChange={(v) => updateRow(index, { value: v })} className="value-secret" />
<SecretValue
value={row.value}
placeholder="Value"
editByDefault={secretEditByDefault}
multiline={editableDataType}
onChange={(v) => updateRow(index, { value: v })}
className="value-secret"
/>
) : (
<textarea
className="value-input"
Expand All @@ -80,14 +87,14 @@ const EnvVarCards: React.FC<EnvVarCardsProps> = ({
onChange={(e) => updateRow(index, { value: e.target.value })}
/>
)}
{editableDataType && !isBlankRow ? (
{editableDataType && !isBlankRow && row.value && (
<VariableTypeControl
dataType={toDataType(row.dataType)}
value={row.value}
index={index}
onChange={(type) => updateRow(index, { dataType: type })}
/>
) : null}
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ export const StyledWrapper = styled.div`

.env-card .enabled {
margin-top: 2px;
cursor: pointer;
accent-color: var(--oc-colors-accent);
flex-shrink: 0;
}

.env-card .body {
Expand Down Expand Up @@ -93,6 +90,7 @@ export const StyledWrapper = styled.div`

.env-card .value .var-type {
flex-shrink: 0;
margin-left: auto;
}

.env-card .delete {
Expand Down
6 changes: 6 additions & 0 deletions packages/oc-docs/src/styles/theme.generated.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
--oc-input-focus-border: #8b8b8b;
--oc-input-placeholder-color: #B0B0B0;
--oc-input-placeholder-opacity: 0.8;
--oc-checkbox-border: #e5e5e5;
--oc-checkbox-checked-bg: hsl(33, 80%, 46%);
--oc-checkbox-check-color: #ffffff;
--oc-sidebar-color: #343434;
--oc-sidebar-muted: #838383;
--oc-sidebar-bg: #f8f8f8;
Expand Down Expand Up @@ -402,6 +405,9 @@
--oc-input-focus-border: rgba(228,174,73,0.8);
--oc-input-placeholder-color: #aaa;
--oc-input-placeholder-opacity: 0.6;
--oc-checkbox-border: #333333;
--oc-checkbox-checked-bg: hsl(39, 74%, 59%);
--oc-checkbox-check-color: #000000;
--oc-sidebar-color: hsl(0deg 0% 80%);
--oc-sidebar-muted: #aaa;
--oc-sidebar-bg: hsl(0deg 0% 10%);
Expand Down
6 changes: 6 additions & 0 deletions packages/oc-docs/src/theme/tokens/dark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ export const dark: Theme = {
}
},

checkbox: {
border: p.border.BORDER1,
checkedBg: p.primary.SOLID,
checkColor: p.utility.BLACK
},

sidebar: {
color: p.text.BASE,
muted: p.text.SUBTEXT1,
Expand Down
6 changes: 6 additions & 0 deletions packages/oc-docs/src/theme/tokens/light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ export const light: Theme = {
}
},

checkbox: {
border: p.border.BORDER1,
checkedBg: p.primary.SOLID,
checkColor: p.utility.WHITE
},

sidebar: {
color: p.text.BASE,
muted: p.text.SUBTEXT1,
Expand Down
29 changes: 29 additions & 0 deletions packages/oc-docs/src/ui/Checkbox/Checkbox.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { describe, it, expect } from 'vitest';
import { useRenderToDom } from '../../hooks/useRenderToDom';
import { getByTestId } from '../../test-utils/dom';
import Checkbox from './Checkbox';

const noop = () => {};

describe('Checkbox', () => {
it('renders a checkbox input carrying the aria-label', () => {
const root = useRenderToDom(<Checkbox testId="cb" checked={false} ariaLabel="Enable host" onChange={noop} />);
const input = getByTestId(root, 'cb-input');
expect(input.getAttribute('type')).toBe('checkbox');
expect(input.getAttribute('aria-label')).toBe('Enable host');
});

it('reflects the checked prop on the input', () => {
const checked = useRenderToDom(<Checkbox testId="cb" checked ariaLabel="Enable host" onChange={noop} />);
expect(getByTestId(checked, 'cb-input').hasAttribute('checked')).toBe(true);

const unchecked = useRenderToDom(<Checkbox testId="cb" checked={false} ariaLabel="Enable host" onChange={noop} />);
expect(getByTestId(unchecked, 'cb-input').hasAttribute('checked')).toBe(false);
});

it('applies the provided className to the wrapper', () => {
const root = useRenderToDom(<Checkbox testId="cb" className="enabled" checked ariaLabel="Enable host" onChange={noop} />);
expect(getByTestId(root, 'cb').classList.contains('enabled')).toBe(true);
});
});
34 changes: 34 additions & 0 deletions packages/oc-docs/src/ui/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { ChangeEvent } from "react";
import { StyledWrapper } from "./StyledWrapper";
import { CheckIcon } from "../../assets/icons";

interface CheckboxProps {
className?: string;
checked: boolean;
ariaLabel: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
testId?: string;
}

const Checkbox: React.FC<CheckboxProps> = ({
className,
checked,
ariaLabel,
onChange,
testId
}) => {
return (
<StyledWrapper className={className} data-testid={testId}>
<input
type="checkbox"
checked={checked}
aria-label={ariaLabel}
onChange={onChange}
data-testid={testId && `${testId}-input`}
/>
<CheckIcon className='checkbox-check' width={12} height={12} />
</StyledWrapper>
);
}

export default Checkbox;
Loading
Loading