M6-T10: frontend contract management + price/cost views + Tibber test
- EnergyPage: Mantine Tabs (Devices kept intact + Contracts/Prices/Costs). - ContractManager/ContractForm: list/activate/add-version + version history; form fields rendered dynamically from /api/energy/profiles structure. - TibberPrices + CostView: Recharts price curve, cost trend/detail/summary, recompute; window-bounded, currency/units from API, empty/error/loading states. - ConfigPage: tri-state Tibber test button (token never shown). - hooks.ts: typed energy hooks; schema.d.ts regenerated via codegen.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Tests for TibberPrices component.
|
||||
*
|
||||
* Coverage:
|
||||
* 1. Loading state.
|
||||
* 2. Empty state (no active contract / no kind).
|
||||
* 3. Renders tibber chart when tibber kind data is available.
|
||||
* 4. Shows tariff table for manual kind.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../test-utils'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock apiClient
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mockGet = vi.fn()
|
||||
|
||||
vi.mock('../api/client', () => ({
|
||||
default: {
|
||||
GET: (...args: unknown[]) => mockGet(...args),
|
||||
POST: vi.fn(),
|
||||
PATCH: vi.fn(),
|
||||
DELETE: vi.fn(),
|
||||
},
|
||||
ApiError: class ApiError extends Error {
|
||||
status: number
|
||||
body: unknown
|
||||
constructor(status: number, body: unknown) {
|
||||
super(`API error ${status}`)
|
||||
this.name = 'ApiError'
|
||||
this.status = status
|
||||
this.body = body
|
||||
}
|
||||
},
|
||||
registerLoginRedirect: vi.fn(),
|
||||
}))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Import component
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { TibberPrices } from './TibberPrices'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('TibberPrices', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
|
||||
it('renders loading state initially', () => {
|
||||
mockGet.mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
renderWithProviders(<TibberPrices />)
|
||||
|
||||
expect(screen.getByTestId('prices-loading')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders "no active contract" when kind is missing/null', async () => {
|
||||
mockGet.mockResolvedValue({
|
||||
data: {
|
||||
kind: null,
|
||||
currency: 'EUR',
|
||||
points: [],
|
||||
tariff: null,
|
||||
},
|
||||
})
|
||||
|
||||
renderWithProviders(<TibberPrices />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('prices-no-contract')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders error state when fetch fails', async () => {
|
||||
mockGet.mockRejectedValue(new Error('Network error'))
|
||||
|
||||
renderWithProviders(<TibberPrices />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('prices-error')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders tibber chart when tibber data is available', async () => {
|
||||
mockGet.mockResolvedValue({
|
||||
data: {
|
||||
kind: 'tibber',
|
||||
currency: 'EUR',
|
||||
points: [
|
||||
{ starts_at: '2026-06-22T10:00:00Z', buy: 0.133, sell: 0.09, level: 'NORMAL' },
|
||||
{ starts_at: '2026-06-22T10:15:00Z', buy: 0.140, sell: 0.092, level: 'NORMAL' },
|
||||
],
|
||||
tariff: null,
|
||||
},
|
||||
})
|
||||
|
||||
renderWithProviders(<TibberPrices />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('tibber-chart')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Check badge shows kind
|
||||
expect(screen.getByText('tibber')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows tariff table for manual kind', async () => {
|
||||
mockGet.mockResolvedValue({
|
||||
data: {
|
||||
kind: 'manual',
|
||||
currency: 'EUR',
|
||||
points: [],
|
||||
tariff: {
|
||||
buy_dal: 0.127,
|
||||
buy_normal: 0.133,
|
||||
sell_dal: 0.09,
|
||||
sell_normal: 0.09,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
renderWithProviders(<TibberPrices />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('manual-tariff-table')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
expect(screen.getByTestId('tariff-buy-normal')).toHaveTextContent('0.1330')
|
||||
expect(screen.getByTestId('tariff-buy-dal')).toHaveTextContent('0.1270')
|
||||
expect(screen.getByTestId('tariff-sell-normal')).toHaveTextContent('0.0900')
|
||||
expect(screen.getByTestId('tariff-sell-dal')).toHaveTextContent('0.0900')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user