Split history chart render owners

This commit is contained in:
rcourtman
2026-03-23 02:20:31 +00:00
parent 0809b0271a
commit 70d34324fe
9 changed files with 286 additions and 173 deletions
@@ -222,6 +222,11 @@ and hover state, and `frontend-modern/src/components/shared/historyChartModel.ts
owns tooltip formatting, scale and axis math, and closest-point selection.
Future history-chart work should extend those owners instead of pushing fetch,
license, or canvas math back into the shared component shell.
The remaining header, overlay, and tooltip render surfaces now live in
`frontend-modern/src/components/shared/HistoryChartHeader.tsx`,
`frontend-modern/src/components/shared/HistoryChartOverlay.tsx`, and
`frontend-modern/src/components/shared/HistoryChartTooltip.tsx` instead of
re-accumulating those sections inline in the shell.
The shared container update badge now follows that same owner split.
`frontend-modern/src/components/shared/ContainerUpdateBadge.tsx` stays the
render surface for the badge, icon, and update button shells,
@@ -1,6 +1,8 @@
import { Component, Show } from 'solid-js';
import { Portal } from 'solid-js/web';
import { formatHistoryChartTooltipValue, type HistoryChartProps } from './historyChartModel';
import { Component } from 'solid-js';
import type { HistoryChartProps } from './historyChartModel';
import { HistoryChartHeader } from './HistoryChartHeader';
import { HistoryChartOverlay } from './HistoryChartOverlay';
import { HistoryChartTooltip } from './HistoryChartTooltip';
import { useHistoryChartState } from './useHistoryChartState';
export type { HistoryChartProps } from './historyChartModel';
@@ -18,65 +20,7 @@ export const HistoryChart: Component<HistoryChartProps> = (props) => {
<div
class={`flex flex-col h-full ${props.compact ? '' : 'bg-surface rounded-md shadow-sm border border-border p-4'}`}
>
<div class={`flex items-center justify-between ${props.compact ? 'mb-2' : 'mb-4'}`}>
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-base-content">{props.label || 'History'}</span>
<Show when={props.unit}>
<span class="text-xs text-slate-400">({props.unit})</span>
</Show>
<Show when={chart.source() && chart.source() !== 'store'}>
<span
class={`text-[10px] font-semibold px-2 py-0.5 rounded-full uppercase tracking-wide ${
chart.source() === 'live'
? 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'
: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300'
}`}
title={
chart.source() === 'live'
? 'Live sample shown because history is not available yet.'
: 'In-memory buffer shown while history is warming up.'
}
>
{chart.source() === 'live' ? 'Live' : 'Buffer'}
</span>
</Show>
</div>
<div class="flex items-center gap-3">
<Show when={chart.dataMin() !== null && chart.dataMax() !== null}>
<div class="flex items-center gap-2 text-[10px]">
<span>
<span class="text-muted">Min </span>
<span class="text-blue-400">
{formatHistoryChartTooltipValue(chart.dataMin()!, props.unit)}
</span>
</span>
<span>
<span class="text-muted">Max </span>
<span class="text-red-400">
{formatHistoryChartTooltipValue(chart.dataMax()!, props.unit)}
</span>
</span>
</div>
</Show>
<Show when={!props.hideSelector}>
<div class="flex bg-surface-hover rounded-md p-0.5">
{chart.ranges.map((range) => (
<button
onClick={() => chart.updateRange(range)}
class={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
chart.range() === range
? 'bg-surface text-base-content shadow-sm'
: 'text-muted hover:text-base-content'
}`}
>
{range}
</button>
))}
</div>
</Show>
</div>
</div>
<HistoryChartHeader chart={chart} compact={props.compact} hideSelector={props.hideSelector} label={props.label} unit={props.unit} />
<div
class={`relative flex-1 w-full ${props.compact ? 'min-h-[120px]' : 'min-h-[200px]'}`}
@@ -88,118 +32,10 @@ export const HistoryChart: Component<HistoryChartProps> = (props) => {
onMouseMove={chart.handleMouseMove}
onMouseLeave={chart.handleMouseLeave}
/>
<Show when={!chart.loading() && chart.data().length === 0 && !chart.error()}>
<div class="absolute inset-0 flex items-center justify-center bg-surface">
<div class="text-center">
<div class="text-slate-400 mb-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mx-auto"
>
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
<path d="M16 16l5 5" />
<path d="M21 21v-5h-5" />
</svg>
</div>
<p class="text-sm text-slate-500">Collecting data... History will appear here.</p>
</div>
</div>
</Show>
<Show when={chart.loading()}>
<div class="absolute inset-0 flex items-center justify-center bg-surface -[1px]">
<div class="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
</div>
</Show>
<Show when={chart.error()}>
<div class="absolute inset-0 flex items-center justify-center">
<p class="text-sm text-red-500">{chart.error()}</p>
</div>
</Show>
<Show when={chart.isLocked() && !props.hideLock}>
<div class="absolute inset-0 z-10 flex flex-col items-center justify-center bg-surface rounded-md">
<div class="bg-indigo-500 rounded-full p-3 shadow-sm mb-3">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</div>
<h3 class="text-lg font-bold text-base-content mb-1">{chart.lockDays()}-Day History</h3>
<p class="text-sm text-muted text-center max-w-[200px] mb-4">
Upgrade to {chart.lockTierLabel()} to unlock {chart.lockDays()} days of historical
data retention.
</p>
<div class="flex flex-col items-center gap-2">
<a
href={chart.getUpgradeActionUrlOrFallback('long_term_metrics')}
target="_blank"
rel="noopener noreferrer"
onClick={() => chart.trackUpgradeClicked('history_chart', 'long_term_metrics')}
class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-md shadow-sm transition-colors"
>
Unlock {chart.lockTierLabel()} Features
</a>
<Show when={chart.canStartTrial()}>
<button
type="button"
class="text-xs font-semibold text-indigo-700 dark:text-indigo-300 hover:underline disabled:opacity-60"
disabled={chart.startingTrial()}
onClick={chart.handleStartTrial}
>
Or start a free 14-day trial
</button>
</Show>
</div>
</div>
</Show>
<HistoryChartOverlay chart={chart} hideLock={props.hideLock} />
</div>
<Portal>
<Show when={chart.hoveredPoint()}>
{(point) => (
<div
class="fixed pointer-events-none text-xs rounded px-2 py-1 shadow-lg border border-slate-600 z-[9999]"
style={{
left: `${point().x}px`,
top: `${point().y}px`,
transform: 'translateX(-50%)',
'background-color': 'rgb(15, 23, 42)',
color: 'rgb(248, 250, 252)',
}}
>
<div class="font-medium text-center mb-0.5">
{new Date(point().timestamp).toLocaleString()}
</div>
<div style={{ color: 'rgb(203, 213, 225)' }}>
{formatHistoryChartTooltipValue(point().value, props.unit)}
</div>
</div>
)}
</Show>
</Portal>
<HistoryChartTooltip hoveredPoint={chart.hoveredPoint()} unit={props.unit} />
</div>
);
};
@@ -0,0 +1,75 @@
import { Component, Show } from 'solid-js';
import { formatHistoryChartTooltipValue } from './historyChartModel';
import type { HistoryChartState } from './useHistoryChartState';
interface HistoryChartHeaderProps {
chart: HistoryChartState;
compact?: boolean;
hideSelector?: boolean;
label?: string;
unit?: string;
}
export const HistoryChartHeader: Component<HistoryChartHeaderProps> = (props) => {
return (
<div class={`flex items-center justify-between ${props.compact ? 'mb-2' : 'mb-4'}`}>
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-base-content">{props.label || 'History'}</span>
<Show when={props.unit}>
<span class="text-xs text-slate-400">({props.unit})</span>
</Show>
<Show when={props.chart.source() && props.chart.source() !== 'store'}>
<span
class={`text-[10px] font-semibold px-2 py-0.5 rounded-full uppercase tracking-wide ${
props.chart.source() === 'live'
? 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'
: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300'
}`}
title={
props.chart.source() === 'live'
? 'Live sample shown because history is not available yet.'
: 'In-memory buffer shown while history is warming up.'
}
>
{props.chart.source() === 'live' ? 'Live' : 'Buffer'}
</span>
</Show>
</div>
<div class="flex items-center gap-3">
<Show when={props.chart.dataMin() !== null && props.chart.dataMax() !== null}>
<div class="flex items-center gap-2 text-[10px]">
<span>
<span class="text-muted">Min </span>
<span class="text-blue-400">
{formatHistoryChartTooltipValue(props.chart.dataMin()!, props.unit)}
</span>
</span>
<span>
<span class="text-muted">Max </span>
<span class="text-red-400">
{formatHistoryChartTooltipValue(props.chart.dataMax()!, props.unit)}
</span>
</span>
</div>
</Show>
<Show when={!props.hideSelector}>
<div class="flex bg-surface-hover rounded-md p-0.5">
{props.chart.ranges.map((range) => (
<button
onClick={() => props.chart.updateRange(range)}
class={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
props.chart.range() === range
? 'bg-surface text-base-content shadow-sm'
: 'text-muted hover:text-base-content'
}`}
>
{range}
</button>
))}
</div>
</Show>
</div>
</div>
);
};
@@ -0,0 +1,100 @@
import { Component, Show } from 'solid-js';
import type { HistoryChartState } from './useHistoryChartState';
interface HistoryChartOverlayProps {
chart: HistoryChartState;
hideLock?: boolean;
}
export const HistoryChartOverlay: Component<HistoryChartOverlayProps> = (props) => {
return (
<>
<Show when={!props.chart.loading() && props.chart.data().length === 0 && !props.chart.error()}>
<div class="absolute inset-0 flex items-center justify-center bg-surface">
<div class="text-center">
<div class="text-slate-400 mb-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mx-auto"
>
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
<path d="M16 16l5 5" />
<path d="M21 21v-5h-5" />
</svg>
</div>
<p class="text-sm text-slate-500">Collecting data... History will appear here.</p>
</div>
</div>
</Show>
<Show when={props.chart.loading()}>
<div class="absolute inset-0 flex items-center justify-center bg-surface -[1px]">
<div class="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
</div>
</Show>
<Show when={props.chart.error()}>
<div class="absolute inset-0 flex items-center justify-center">
<p class="text-sm text-red-500">{props.chart.error()}</p>
</div>
</Show>
<Show when={props.chart.isLocked() && !props.hideLock}>
<div class="absolute inset-0 z-10 flex flex-col items-center justify-center bg-surface rounded-md">
<div class="bg-indigo-500 rounded-full p-3 shadow-sm mb-3">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</div>
<h3 class="text-lg font-bold text-base-content mb-1">{props.chart.lockDays()}-Day History</h3>
<p class="text-sm text-muted text-center max-w-[200px] mb-4">
Upgrade to {props.chart.lockTierLabel()} to unlock {props.chart.lockDays()} days of
historical data retention.
</p>
<div class="flex flex-col items-center gap-2">
<a
href={props.chart.getUpgradeActionUrlOrFallback('long_term_metrics')}
target="_blank"
rel="noopener noreferrer"
onClick={() => props.chart.trackUpgradeClicked('history_chart', 'long_term_metrics')}
class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-md shadow-sm transition-colors"
>
Unlock {props.chart.lockTierLabel()} Features
</a>
<Show when={props.chart.canStartTrial()}>
<button
type="button"
class="text-xs font-semibold text-indigo-700 dark:text-indigo-300 hover:underline disabled:opacity-60"
disabled={props.chart.startingTrial()}
onClick={props.chart.handleStartTrial}
>
Or start a free 14-day trial
</button>
</Show>
</div>
</div>
</Show>
</>
);
};
@@ -0,0 +1,36 @@
import { Component, Show } from 'solid-js';
import { Portal } from 'solid-js/web';
import { formatHistoryChartTooltipValue, type HistoryChartHoverPoint } from './historyChartModel';
interface HistoryChartTooltipProps {
hoveredPoint: HistoryChartHoverPoint | null;
unit?: string;
}
export const HistoryChartTooltip: Component<HistoryChartTooltipProps> = (props) => {
return (
<Portal>
<Show when={props.hoveredPoint}>
{(point) => (
<div
class="fixed pointer-events-none text-xs rounded px-2 py-1 shadow-lg border border-slate-600 z-[9999]"
style={{
left: `${point().x}px`,
top: `${point().y}px`,
transform: 'translateX(-50%)',
'background-color': 'rgb(15, 23, 42)',
color: 'rgb(248, 250, 252)',
}}
>
<div class="font-medium text-center mb-0.5">
{new Date(point().timestamp).toLocaleString()}
</div>
<div style={{ color: 'rgb(203, 213, 225)' }}>
{formatHistoryChartTooltipValue(point().value, props.unit)}
</div>
</div>
)}
</Show>
</Portal>
);
};
@@ -9,8 +9,11 @@ import densityMapModelSource from '@/components/shared/densityMapModel.ts?raw';
import filterButtonGroupSource from '@/components/shared/FilterButtonGroup.tsx?raw';
import helpIconSource from '@/components/shared/HelpIcon.tsx?raw';
import helpIconModelSource from '@/components/shared/helpIconModel.ts?raw';
import historyChartHeaderSource from '@/components/shared/HistoryChartHeader.tsx?raw';
import historyChartOverlaySource from '@/components/shared/HistoryChartOverlay.tsx?raw';
import historyChartSource from '@/components/shared/HistoryChart.tsx?raw';
import historyChartModelSource from '@/components/shared/historyChartModel.ts?raw';
import historyChartTooltipSource from '@/components/shared/HistoryChartTooltip.tsx?raw';
import infrastructureDetailsDrawerSource from '@/components/shared/InfrastructureDetailsDrawer.tsx?raw';
import infrastructureDetailsDrawerModelSource from '@/components/shared/infrastructureDetailsDrawerModel.ts?raw';
import mobileNavBarSource from '@/components/shared/MobileNavBar.tsx?raw';
@@ -247,19 +250,39 @@ describe('shared primitive guardrails', () => {
it('keeps history chart on shell, runtime, and model owners', () => {
expect(historyChartSource).toContain('useHistoryChartState');
expect(historyChartSource).toContain('HistoryChartHeader');
expect(historyChartSource).toContain('HistoryChartOverlay');
expect(historyChartSource).toContain('HistoryChartTooltip');
expect(historyChartSource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartSource).not.toContain('calculateOptimalPoints');
expect(historyChartSource).not.toContain('setupCanvasDPR');
expect(historyChartSource).not.toContain('createSignal');
expect(historyChartSource).not.toContain('Collecting data... History will appear here.');
expect(historyChartSource).not.toContain('Unlock {chart.lockTierLabel()} Features');
expect(historyChartStateSource).toContain('ChartsAPI.getMetricsHistory');
expect(historyChartStateSource).toContain('calculateOptimalPoints');
expect(historyChartStateSource).toContain('setupCanvasDPR');
expect(historyChartStateSource).toContain('export function useHistoryChartState');
expect(historyChartStateSource).toContain('HISTORY_CHART_RANGES');
expect(historyChartModelSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartModelSource).toContain('HISTORY_CHART_RANGES');
expect(historyChartModelSource).toContain('getHistoryChartScale');
expect(historyChartModelSource).toContain('findHistoryChartClosestPoint');
expect(historyChartHeaderSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartHeaderSource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartHeaderSource).not.toContain('setupCanvasDPR');
expect(historyChartOverlaySource).toContain('Collecting data... History will appear here.');
expect(historyChartOverlaySource).toContain('Unlock {props.chart.lockTierLabel()} Features');
expect(historyChartOverlaySource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartOverlaySource).not.toContain('setupCanvasDPR');
expect(historyChartTooltipSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartTooltipSource).toContain('new Date(point().timestamp).toLocaleString()');
expect(historyChartTooltipSource).not.toContain('ChartsAPI.getMetricsHistory');
});
it('keeps container update badge on shell, runtime, and model owners', () => {
@@ -1,8 +1,11 @@
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@solidjs/testing-library';
import historyChartHeaderSource from '@/components/shared/HistoryChartHeader.tsx?raw';
import historyChartOverlaySource from '@/components/shared/HistoryChartOverlay.tsx?raw';
import historyChartSource from '@/components/shared/HistoryChart.tsx?raw';
import historyChartModelSource from '@/components/shared/historyChartModel.ts?raw';
import historyChartStateSource from '@/components/shared/useHistoryChartState.ts?raw';
import historyChartTooltipSource from '@/components/shared/HistoryChartTooltip.tsx?raw';
import { HistoryChart } from '@/components/shared/HistoryChart';
if (typeof globalThis.ResizeObserver === 'undefined') {
@@ -50,19 +53,39 @@ vi.mock('@/api/charts', () => ({
describe('HistoryChart', () => {
it('keeps the history chart on shell, runtime, and model owners', () => {
expect(historyChartSource).toContain('useHistoryChartState');
expect(historyChartSource).toContain('HistoryChartHeader');
expect(historyChartSource).toContain('HistoryChartOverlay');
expect(historyChartSource).toContain('HistoryChartTooltip');
expect(historyChartSource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartSource).not.toContain('calculateOptimalPoints');
expect(historyChartSource).not.toContain('setupCanvasDPR');
expect(historyChartSource).not.toContain('createSignal');
expect(historyChartSource).not.toContain('Collecting data... History will appear here.');
expect(historyChartSource).not.toContain('Unlock {chart.lockTierLabel()} Features');
expect(historyChartStateSource).toContain('ChartsAPI.getMetricsHistory');
expect(historyChartStateSource).toContain('calculateOptimalPoints');
expect(historyChartStateSource).toContain('setupCanvasDPR');
expect(historyChartStateSource).toContain('export function useHistoryChartState');
expect(historyChartStateSource).toContain('HISTORY_CHART_RANGES');
expect(historyChartModelSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartModelSource).toContain('HISTORY_CHART_RANGES');
expect(historyChartModelSource).toContain('getHistoryChartScale');
expect(historyChartModelSource).toContain('findHistoryChartClosestPoint');
expect(historyChartHeaderSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartHeaderSource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartHeaderSource).not.toContain('setupCanvasDPR');
expect(historyChartOverlaySource).toContain('Collecting data... History will appear here.');
expect(historyChartOverlaySource).toContain('Unlock {props.chart.lockTierLabel()} Features');
expect(historyChartOverlaySource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartOverlaySource).not.toContain('setupCanvasDPR');
expect(historyChartTooltipSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartTooltipSource).toContain('new Date(point().timestamp).toLocaleString()');
expect(historyChartTooltipSource).not.toContain('ChartsAPI.getMetricsHistory');
});
it('renders the default history label', () => {
@@ -25,6 +25,7 @@ import {
getTrialTryAgainLaterMessage,
} from '@/utils/upgradePresentation';
import {
HISTORY_CHART_RANGES,
createHistoryChartGeometry,
findHistoryChartClosestPoint,
formatHistoryChartTimeLabel,
@@ -417,7 +418,7 @@ export function useHistoryChartState(props: HistoryChartProps, refs: HistoryChar
lockDays,
lockTierLabel,
range,
ranges: ['24h', '7d', '30d', '90d'] as HistoryTimeRange[],
ranges: HISTORY_CHART_RANGES,
source,
startingTrial,
trackUpgradeClicked,
@@ -16,8 +16,11 @@ import densityMapSource from '@/components/shared/DensityMap.tsx?raw';
import densityMapModelSource from '@/components/shared/densityMapModel.ts?raw';
import helpIconSource from '@/components/shared/HelpIcon.tsx?raw';
import helpIconModelSource from '@/components/shared/helpIconModel.ts?raw';
import historyChartHeaderSource from '@/components/shared/HistoryChartHeader.tsx?raw';
import historyChartOverlaySource from '@/components/shared/HistoryChartOverlay.tsx?raw';
import historyChartSource from '@/components/shared/HistoryChart.tsx?raw';
import historyChartModelSource from '@/components/shared/historyChartModel.ts?raw';
import historyChartTooltipSource from '@/components/shared/HistoryChartTooltip.tsx?raw';
import mobileNavBarSource from '@/components/shared/MobileNavBar.tsx?raw';
import mobileNavBarModelSource from '@/components/shared/mobileNavBarModel.ts?raw';
import infrastructureSelectorSource from '@/components/shared/InfrastructureSelector.tsx?raw';
@@ -2713,14 +2716,25 @@ describe('frontend resource type boundaries', () => {
expect(densityMapModelSource).toContain('formatDensityMapHoverTime');
expect(densityMapModelSource).toContain('getDensityMapCellOpacity');
expect(historyChartSource).toContain('useHistoryChartState');
expect(historyChartSource).toContain('HistoryChartHeader');
expect(historyChartSource).toContain('HistoryChartOverlay');
expect(historyChartSource).toContain('HistoryChartTooltip');
expect(historyChartSource).not.toContain('ChartsAPI.getMetricsHistory');
expect(historyChartSource).not.toContain('calculateOptimalPoints');
expect(historyChartSource).not.toContain('setupCanvasDPR');
expect(historyChartSource).not.toContain('Collecting data... History will appear here.');
expect(historyChartSource).not.toContain('Unlock {chart.lockTierLabel()} Features');
expect(historyChartStateSource).toContain('ChartsAPI.getMetricsHistory');
expect(historyChartStateSource).toContain('calculateOptimalPoints');
expect(historyChartStateSource).toContain('setupCanvasDPR');
expect(historyChartStateSource).toContain('HISTORY_CHART_RANGES');
expect(historyChartModelSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartModelSource).toContain('HISTORY_CHART_RANGES');
expect(historyChartModelSource).toContain('getHistoryChartScale');
expect(historyChartHeaderSource).toContain('formatHistoryChartTooltipValue');
expect(historyChartOverlaySource).toContain('Collecting data... History will appear here.');
expect(historyChartOverlaySource).toContain('Unlock {props.chart.lockTierLabel()} Features');
expect(historyChartTooltipSource).toContain('formatHistoryChartTooltipValue');
expect(containerUpdateBadgeSource).toContain('useContainerUpdateButtonState');
expect(containerUpdateBadgeSource).toContain('getUpdateButtonClass');
expect(containerUpdateBadgeSource).not.toContain('MonitoringAPI.updateDockerContainer');