mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-25 12:52:25 +00:00
Split history chart render owners
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user