Files
headlamp-intel-gpu-plugin/src/components/PodDetailSection.test.tsx
T
privilegedescalation-engineer[bot] 6cd159b5a4 test: add component test coverage for all untested files (#17)
* test: add component test coverage for all untested files

Adds 60 new tests (108 total) covering every untested module:
- IntelGpuDataContext: provider renders, loading/loaded states, CRD
  available/unavailable paths, refresh, useIntelGpuContext throws outside
  provider
- OverviewPage: loading, plugin-not-detected, error, populated, refresh
  button, CRD notice, device plugin table, plugin daemon pods, active pods
- NodesPage: loading, empty state, GPU node summary table, detail cards
- PodsPage: loading, empty state, summary counts, pending pod attention,
  all-pods table
- DevicePluginsPage: loading, CRD unavailable, no-plugins, plugin detail,
  daemon pod table
- NodeDetailSection: null for non-GPU nodes, GPU capacity/allocatable rows,
  pod list, loading state
- PodDetailSection: null for non-GPU pods, GPU resource rows, phase status,
  limits-only containers
- MetricsPage: context loading gate, Prometheus unreachable, empty chips,
  chip cards with power values, MetricRequirements always rendered, refresh

Also fixes vitest.config.mts to pin NODE_ENV=test so tests run correctly
without requiring callers to set it explicitly.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: remove unused act import and merge duplicate metrics imports in MetricsPage.test.tsx

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: cast useList mock return values to any in IntelGpuDataContext.test.tsx

The Headlamp useList() return type is an intersection of a tuple and
QueryListResponse, which plain array literals like [[], null] and
[null, null] do not satisfy. Cast all useList mockReturnValue arguments
to any so tsc passes without requiring full KubeObject stub objects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: run Prettier formatting and ESLint lint:fix on test files

Addresses CI format:check failures and import-sort warning in
MetricsPage.test.tsx flagged by QA on PR #17.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Hugh Hackman <hugh@privilegedescalation.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.com>
Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Gandalf the Greybeard <gandalf-the-greybeard[bot]@users.noreply.github.com>
2026-03-21 12:53:04 +00:00

139 lines
4.4 KiB
TypeScript

import { render, screen } from '@testing-library/react';
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import PodDetailSection from './PodDetailSection';
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
SectionBox: ({ title, children }: { title: string; children?: React.ReactNode }) => (
<section>
<h2>{title}</h2>
{children}
</section>
),
NameValueTable: ({
rows,
}: {
rows: Array<{ name: React.ReactNode; value: React.ReactNode }>;
}) => (
<dl>
{rows.map((r, i) => (
<div key={i}>
<dt>{r.name}</dt>
<dd>{r.value}</dd>
</div>
))}
</dl>
),
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
<span data-status={status}>{children}</span>
),
}));
// PodDetailSection does NOT use the context — no need to mock IntelGpuDataContext
// A non-GPU pod (no gpu.intel.com resources)
const nonGpuPodRaw = {
kind: 'Pod',
metadata: { name: 'plain-pod', namespace: 'default' },
spec: {
containers: [{ name: 'main', resources: { requests: { cpu: '100m', memory: '128Mi' } } }],
},
status: { phase: 'Running' },
};
// A GPU-requesting pod
const gpuPodRaw = {
kind: 'Pod',
metadata: { name: 'gpu-workload', namespace: 'default' },
spec: {
nodeName: 'gpu-node-1',
containers: [
{
name: 'trainer',
resources: {
requests: { 'gpu.intel.com/i915': '1', cpu: '2' },
limits: { 'gpu.intel.com/i915': '1', cpu: '2' },
},
},
],
},
status: { phase: 'Running' },
};
// A pod with limits only (no requests)
const gpuPodLimitsOnly = {
kind: 'Pod',
metadata: { name: 'limits-only-pod', namespace: 'default' },
spec: {
containers: [
{
name: 'app',
resources: {
limits: { 'gpu.intel.com/i915': '1' },
},
},
],
},
status: { phase: 'Pending' },
};
describe('PodDetailSection', () => {
it('renders nothing for a non-GPU pod', () => {
const { container } = render(<PodDetailSection resource={nonGpuPodRaw} />);
expect(container).toBeEmptyDOMElement();
});
it('renders nothing for a non-GPU pod passed via jsonData', () => {
const { container } = render(<PodDetailSection resource={{ jsonData: nonGpuPodRaw }} />);
expect(container).toBeEmptyDOMElement();
});
it('renders "Intel GPU Resources" section for a GPU-requesting pod via jsonData', () => {
render(<PodDetailSection resource={{ jsonData: gpuPodRaw }} />);
expect(screen.getByText('Intel GPU Resources')).toBeInTheDocument();
});
it('renders "Intel GPU Resources" section for a GPU-requesting pod provided directly', () => {
render(<PodDetailSection resource={gpuPodRaw} />);
expect(screen.getByText('Intel GPU Resources')).toBeInTheDocument();
});
it('shows container GPU resource request rows', () => {
render(<PodDetailSection resource={gpuPodRaw} />);
// Row label: "{containerName} → {resourceName} request"
expect(screen.getByText('trainer → GPU (i915) request')).toBeInTheDocument();
});
it('shows phase status label for Running phase', () => {
render(<PodDetailSection resource={gpuPodRaw} />);
const statusEl = screen.getByText('Running');
expect(statusEl).toHaveAttribute('data-status', 'success');
});
it('shows phase status label for Pending phase', () => {
render(<PodDetailSection resource={gpuPodLimitsOnly} />);
const statusEl = screen.getByText('Pending');
expect(statusEl).toHaveAttribute('data-status', 'warning');
});
it('still renders when a container has limits only and no requests', () => {
render(<PodDetailSection resource={gpuPodLimitsOnly} />);
expect(screen.getByText('Intel GPU Resources')).toBeInTheDocument();
// limits-only pod: the request row should show '—' since requests key is absent
expect(screen.getByText('app → GPU (i915) request')).toBeInTheDocument();
});
it('shows scheduled node name', () => {
render(<PodDetailSection resource={gpuPodRaw} />);
expect(screen.getByText('gpu-node-1')).toBeInTheDocument();
});
it('shows GPU container count', () => {
render(<PodDetailSection resource={gpuPodRaw} />);
const label = screen.getByText('GPU Containers');
expect(label).toBeInTheDocument();
// The value '1' is rendered in the sibling <dd>; verify via parent row
expect(label.closest('div')).toHaveTextContent('1');
});
});