feat: add Playwright E2E smoke tests and fix empty namespace crash
Fix getNamespaces() to skip cluster-scoped resources (Namespace: "") that caused Router.createRouteURL to throw TypeError on the Namespaces page. Add Playwright E2E smoke tests with Authentik OIDC auth for CI and K8s token fallback for local dev. Add Gitea Actions E2E workflow, vitest unit test infrastructure, and test-utils fixtures. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { makeAuditData, makeResult } from '../test-utils';
|
||||
|
||||
// Mock Headlamp lib
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn() },
|
||||
}));
|
||||
|
||||
// Mock Headlamp CommonComponents as thin pass-throughs
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
Loader: ({ title }: { title: string }) => <div data-testid="loader">{title}</div>,
|
||||
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
|
||||
<div data-testid="section-box" data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
SectionHeader: ({ title }: { title: string }) => <div data-testid="section-header">{title}</div>,
|
||||
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
|
||||
<span data-testid="status-label" data-status={status}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
NameValueTable: ({ rows }: { rows: Array<{ name: string; value: React.ReactNode }> }) => (
|
||||
<table data-testid="name-value-table">
|
||||
<tbody>
|
||||
{rows.map(row => (
|
||||
<tr key={row.name}>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
PercentageCircle: ({ label }: { label: string }) => (
|
||||
<div data-testid="percentage-circle">{label}</div>
|
||||
),
|
||||
PercentageBar: () => <div data-testid="percentage-bar" />,
|
||||
}));
|
||||
|
||||
// Mock the context hook — we'll override per test via mockReturnValue
|
||||
const mockUsePolarisDataContext = vi.fn();
|
||||
vi.mock('../api/PolarisDataContext', () => ({
|
||||
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
||||
}));
|
||||
|
||||
import DashboardView from './DashboardView';
|
||||
|
||||
describe('DashboardView', () => {
|
||||
it('renders loader when loading', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByTestId('loader')).toHaveTextContent('Loading Polaris audit data');
|
||||
});
|
||||
|
||||
it('renders error message when error is set', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: 'Access denied (403)',
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText('Access denied (403)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders score, check distribution, and cluster info with data', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
c2: {
|
||||
ID: 'c2',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
|
||||
// Score circle shows 50%
|
||||
expect(screen.getByTestId('percentage-circle')).toHaveTextContent('50%');
|
||||
|
||||
// Check distribution values
|
||||
expect(screen.getByText('Total Checks')).toBeInTheDocument();
|
||||
|
||||
// Cluster info section (title is in data-title attr of SectionBox)
|
||||
const sectionBoxes = screen.getAllByTestId('section-box');
|
||||
const clusterInfoBox = sectionBoxes.find(
|
||||
el => el.getAttribute('data-title') === 'Cluster Info'
|
||||
);
|
||||
expect(clusterInfoBox).toBeDefined();
|
||||
|
||||
// Cluster info values
|
||||
expect(screen.getByText('Nodes')).toBeInTheDocument();
|
||||
expect(screen.getByText('Pods')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders "No Data" when no data and no error', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<DashboardView />);
|
||||
expect(screen.getByText('No Polaris audit results found.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { makeAuditData, makeResult } from '../test-utils';
|
||||
|
||||
// Mock Headlamp lib
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn() },
|
||||
}));
|
||||
|
||||
// Mock react-router-dom useParams
|
||||
const mockNamespace = vi.fn(() => 'test-ns');
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useParams: () => ({ namespace: mockNamespace() }),
|
||||
}));
|
||||
|
||||
// Mock Headlamp CommonComponents
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
Loader: ({ title }: { title: string }) => <div data-testid="loader">{title}</div>,
|
||||
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
|
||||
<div data-testid="section-box" data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
SectionHeader: ({ title }: { title: string }) => <div data-testid="section-header">{title}</div>,
|
||||
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
|
||||
<span data-testid="status-label" data-status={status}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
NameValueTable: ({ rows }: { rows: Array<{ name: string; value: React.ReactNode }> }) => (
|
||||
<table data-testid="name-value-table">
|
||||
<tbody>
|
||||
{rows.map(row => (
|
||||
<tr key={row.name}>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
SimpleTable: ({
|
||||
columns,
|
||||
data,
|
||||
emptyMessage,
|
||||
}: {
|
||||
columns: Array<{ label: string; getter: (row: unknown) => React.ReactNode }>;
|
||||
data: unknown[];
|
||||
emptyMessage?: string;
|
||||
}) =>
|
||||
data.length === 0 ? (
|
||||
<div data-testid="simple-table-empty">{emptyMessage}</div>
|
||||
) : (
|
||||
<table data-testid="simple-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map(col => (
|
||||
<th key={col.label}>{col.label}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{columns.map(col => (
|
||||
<td key={col.label}>{col.getter(row)}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockUsePolarisDataContext = vi.fn();
|
||||
vi.mock('../api/PolarisDataContext', () => ({
|
||||
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
||||
}));
|
||||
|
||||
import NamespaceDetailView from './NamespaceDetailView';
|
||||
|
||||
describe('NamespaceDetailView', () => {
|
||||
it('renders loader when loading', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<NamespaceDetailView />);
|
||||
expect(screen.getByTestId('loader')).toHaveTextContent('Loading Polaris data for test-ns');
|
||||
});
|
||||
|
||||
it('renders error message when error is set', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: 'Access denied (403)',
|
||||
});
|
||||
|
||||
render(<NamespaceDetailView />);
|
||||
expect(screen.getByText('Access denied (403)')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('section-header')).toHaveTextContent('Polaris — test-ns');
|
||||
});
|
||||
|
||||
it('renders "No Data" when no data and no error', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<NamespaceDetailView />);
|
||||
expect(screen.getByText('No Polaris audit results found.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders namespace score and resource table with data', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-a',
|
||||
Namespace: 'test-ns',
|
||||
Kind: 'Deployment',
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
c2: {
|
||||
ID: 'c2',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
makeResult({
|
||||
Name: 'other',
|
||||
Namespace: 'other-ns',
|
||||
Kind: 'Deployment',
|
||||
Results: {
|
||||
c3: {
|
||||
ID: 'c3',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<NamespaceDetailView />);
|
||||
|
||||
// Header
|
||||
expect(screen.getByTestId('section-header')).toHaveTextContent('Polaris — test-ns');
|
||||
|
||||
// Score section: 50% (1 pass / 2 total)
|
||||
expect(screen.getByText('50%')).toBeInTheDocument();
|
||||
expect(screen.getByText('Total Checks')).toBeInTheDocument();
|
||||
|
||||
// Resource table shows only test-ns resources
|
||||
expect(screen.getByText('deploy-a')).toBeInTheDocument();
|
||||
expect(screen.queryByText('other')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty table message for namespace with no results', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-a',
|
||||
Namespace: 'other-ns',
|
||||
Results: {},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<NamespaceDetailView />);
|
||||
expect(screen.getByTestId('simple-table-empty')).toHaveTextContent(
|
||||
'No resources found in namespace "test-ns"'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,219 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { makeAuditData, makeResult } from '../test-utils';
|
||||
|
||||
// Mock Headlamp lib
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn() },
|
||||
Router: {
|
||||
createRouteURL: (name: string, params: Record<string, string>) =>
|
||||
`/polaris/ns/${params.namespace}`,
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock Headlamp CommonComponents
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
Loader: ({ title }: { title: string }) => <div data-testid="loader">{title}</div>,
|
||||
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
|
||||
<div data-testid="section-box" data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
SectionHeader: ({ title }: { title: string }) => <div data-testid="section-header">{title}</div>,
|
||||
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
|
||||
<span data-testid="status-label" data-status={status}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
NameValueTable: ({ rows }: { rows: Array<{ name: string; value: React.ReactNode }> }) => (
|
||||
<table data-testid="name-value-table">
|
||||
<tbody>
|
||||
{rows.map(row => (
|
||||
<tr key={row.name}>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
SimpleTable: ({
|
||||
columns,
|
||||
data,
|
||||
emptyMessage,
|
||||
}: {
|
||||
columns: Array<{ label: string; getter: (row: unknown) => React.ReactNode }>;
|
||||
data: unknown[];
|
||||
emptyMessage?: string;
|
||||
}) =>
|
||||
data.length === 0 ? (
|
||||
<div data-testid="simple-table-empty">{emptyMessage}</div>
|
||||
) : (
|
||||
<table data-testid="simple-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map(col => (
|
||||
<th key={col.label}>{col.label}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{columns.map(col => (
|
||||
<td key={col.label}>{col.getter(row)}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockUsePolarisDataContext = vi.fn();
|
||||
vi.mock('../api/PolarisDataContext', () => ({
|
||||
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
||||
}));
|
||||
|
||||
import NamespacesListView from './NamespacesListView';
|
||||
|
||||
function renderWithRouter(ui: React.ReactElement) {
|
||||
return render(<MemoryRouter>{ui}</MemoryRouter>);
|
||||
}
|
||||
|
||||
describe('NamespacesListView', () => {
|
||||
it('renders loader when loading', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
expect(screen.getByTestId('loader')).toHaveTextContent('Loading Polaris audit data');
|
||||
});
|
||||
|
||||
it('renders error message when error is set', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: 'Polaris dashboard not reachable',
|
||||
});
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
expect(screen.getByText('Polaris dashboard not reachable')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders "No Data" when no data and no error', () => {
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
expect(screen.getByText('No Polaris audit results found.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders namespace rows with correct scores and links', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-a',
|
||||
Namespace: 'alpha',
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
makeResult({
|
||||
Name: 'deploy-b',
|
||||
Namespace: 'beta',
|
||||
Results: {
|
||||
c2: {
|
||||
ID: 'c2',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
|
||||
// Namespace links
|
||||
const alphaLink = screen.getByText('alpha');
|
||||
expect(alphaLink.closest('a')).toHaveAttribute('href', '/polaris/ns/alpha');
|
||||
|
||||
const betaLink = screen.getByText('beta');
|
||||
expect(betaLink.closest('a')).toHaveAttribute('href', '/polaris/ns/beta');
|
||||
});
|
||||
|
||||
it('uses correct scoreStatus: >=80 success, >=50 warning, <50 error', () => {
|
||||
// Create a namespace with 100% score (1 pass) and one with 0% (1 danger)
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'perfect',
|
||||
Namespace: 'good-ns',
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
makeResult({
|
||||
Name: 'bad',
|
||||
Namespace: 'bad-ns',
|
||||
Results: {
|
||||
c2: {
|
||||
ID: 'c2',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
mockUsePolarisDataContext.mockReturnValue({
|
||||
data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
renderWithRouter(<NamespacesListView />);
|
||||
|
||||
// Find score StatusLabels - good-ns has 100% (success), bad-ns has 0% (error)
|
||||
const statusLabels = screen.getAllByTestId('status-label');
|
||||
const scoreLabels = statusLabels.filter(el => el.textContent?.includes('%'));
|
||||
|
||||
const successScore = scoreLabels.find(el => el.textContent === '100%');
|
||||
expect(successScore).toHaveAttribute('data-status', 'success');
|
||||
|
||||
const errorScore = scoreLabels.find(el => el.textContent === '0%');
|
||||
expect(errorScore).toHaveAttribute('data-status', 'error');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Mock Headlamp lib
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn() },
|
||||
}));
|
||||
|
||||
// Mock Headlamp CommonComponents
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
|
||||
<div data-testid="section-box" data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
NameValueTable: ({ rows }: { rows: Array<{ name: string; value: React.ReactNode }> }) => (
|
||||
<div data-testid="name-value-table">
|
||||
{rows.map(row => (
|
||||
<div key={row.name}>
|
||||
<span>{row.name}</span>
|
||||
<span>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
import { setRefreshInterval } from '../api/polaris';
|
||||
import PolarisSettings from './PolarisSettings';
|
||||
|
||||
describe('PolarisSettings', () => {
|
||||
it('renders with interval from props.data', () => {
|
||||
render(<PolarisSettings data={{ refreshInterval: 60 }} />);
|
||||
|
||||
const select = screen.getByRole('combobox');
|
||||
expect(select).toHaveValue('60');
|
||||
});
|
||||
|
||||
it('falls back to getRefreshInterval when no prop data', () => {
|
||||
// Default is 300 (5 minutes)
|
||||
render(<PolarisSettings />);
|
||||
|
||||
const select = screen.getByRole('combobox');
|
||||
expect(select).toHaveValue('300');
|
||||
});
|
||||
|
||||
it('renders all interval options', () => {
|
||||
render(<PolarisSettings />);
|
||||
|
||||
const options = screen.getAllByRole('option');
|
||||
expect(options).toHaveLength(4);
|
||||
expect(options[0]).toHaveTextContent('1 minute');
|
||||
expect(options[1]).toHaveTextContent('5 minutes');
|
||||
expect(options[2]).toHaveTextContent('10 minutes');
|
||||
expect(options[3]).toHaveTextContent('30 minutes');
|
||||
});
|
||||
|
||||
it('calls setRefreshInterval and onDataChange when selection changes', async () => {
|
||||
const onDataChange = vi.fn();
|
||||
render(<PolarisSettings data={{ refreshInterval: 300 }} onDataChange={onDataChange} />);
|
||||
|
||||
const select = screen.getByRole('combobox');
|
||||
await userEvent.selectOptions(select, '1800');
|
||||
|
||||
// Check localStorage was updated
|
||||
expect(localStorage.getItem('polaris-plugin-refresh-interval')).toBe('1800');
|
||||
|
||||
// Check callback was called with merged data
|
||||
expect(onDataChange).toHaveBeenCalledWith({ refreshInterval: 1800 });
|
||||
});
|
||||
|
||||
it('works without onDataChange callback', async () => {
|
||||
render(<PolarisSettings data={{ refreshInterval: 300 }} />);
|
||||
|
||||
const select = screen.getByRole('combobox');
|
||||
// Should not throw even without onDataChange
|
||||
await userEvent.selectOptions(select, '60');
|
||||
|
||||
expect(localStorage.getItem('polaris-plugin-refresh-interval')).toBe('60');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user