Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/components/charts/BalanceHistoryChart.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,126 @@ describe('BalanceHistoryChart', () => {
expect(latestInteractiveLineChartProps.hideLineWhileLoading).toBe(true);
});

it('preserves an already visible series while chart data is not ready when requested', async () => {
let renderer!: TestRenderer.ReactTestRenderer;
const onSelectedBalanceChange = jest.fn();

await act(async () => {
renderer = TestRenderer.create(
balanceHistoryChart({
showLoaderWhenNoSnapshots: true,
onSelectedBalanceChange,
}),
);
});

expect(latestInteractiveLineChartProps.points).toBe(
mockOneDaySeries.graphPoints,
);
expect(mockRunPortfolioBalanceChartViewModelQuery).toHaveBeenCalledTimes(1);

await act(async () => {
renderer.update(
balanceHistoryChart({
showLoaderWhenNoSnapshots: false,
isBalanceChartDataReadyToQuery: false,
preserveVisibleSeriesWhileNotReady: true,
onSelectedBalanceChange,
}),
);
});

expect(mockRunPortfolioBalanceChartViewModelQuery).toHaveBeenCalledTimes(1);
expect(latestInteractiveLineChartProps.isLoading).toBe(false);
expect(latestInteractiveLineChartProps.points).toBe(
mockOneDaySeries.graphPoints,
);
expect(latestInteractiveLineChartProps.hideLineWhileLoading).toBe(false);
expect(latestInteractiveLineChartProps.enablePanGesture).toBe(true);

await act(async () => {
latestInteractiveLineChartProps.onGestureStart();
latestInteractiveLineChartProps.onPointSelected(mockOneDayPoint);
});

expect(onSelectedBalanceChange).toHaveBeenLastCalledWith(100);
});

it('defers a stale-preserved timeframe switch until chart data is ready', async () => {
jest.useFakeTimers();
mockRunPortfolioBalanceChartViewModelQuery
.mockResolvedValueOnce({__series: mockOneDaySeries})
.mockResolvedValueOnce({__series: mockOneWeekSeries});
let renderer!: TestRenderer.ReactTestRenderer;

await act(async () => {
renderer = TestRenderer.create(
balanceHistoryChart({showLoaderWhenNoSnapshots: true}),
);
});

expect(mockRunPortfolioBalanceChartViewModelQuery).toHaveBeenCalledTimes(1);
expect(latestInteractiveLineChartProps.points).toBe(
mockOneDaySeries.graphPoints,
);

await act(async () => {
renderer.update(
balanceHistoryChart({
showLoaderWhenNoSnapshots: false,
isBalanceChartDataReadyToQuery: false,
preserveVisibleSeriesWhileNotReady: true,
}),
);
});

expect(latestInteractiveLineChartProps.enablePanGesture).toBe(true);

await act(async () => {
latestTimeframeSelectorProps.onSelect('1W');
});

expect(mockRunPortfolioBalanceChartViewModelQuery).toHaveBeenCalledTimes(1);
expect(latestTimeframeSelectorProps.selected).toBe('1W');
expect(latestInteractiveLineChartProps.points).toBe(
mockOneDaySeries.graphPoints,
);
expect(latestInteractiveLineChartProps.enablePanGesture).toBe(false);
expect(latestInteractiveLineChartProps.isLoading).toBe(false);

await act(async () => {
jest.advanceTimersByTime(119);
});

expect(latestInteractiveLineChartProps.isLoading).toBe(false);

await act(async () => {
jest.advanceTimersByTime(1);
});

expect(latestInteractiveLineChartProps.isLoading).toBe(true);

await act(async () => {
renderer.update(
balanceHistoryChart({
showLoaderWhenNoSnapshots: true,
isBalanceChartDataReadyToQuery: true,
}),
);
await Promise.resolve();
});

expect(mockRunPortfolioBalanceChartViewModelQuery).toHaveBeenCalledTimes(2);
expect(
mockRunPortfolioBalanceChartViewModelQuery.mock.calls[1][0].timeframe,
).toBe('1W');
expect(latestInteractiveLineChartProps.points).toBe(
mockOneWeekSeries.graphPoints,
);
expect(latestInteractiveLineChartProps.isLoading).toBe(false);
expect(latestInteractiveLineChartProps.enablePanGesture).toBe(true);
});

it('fades out the axis labels for a zero balance interval', async () => {
mockRunPortfolioBalanceChartViewModelQuery.mockResolvedValue({
__series: mockZeroBalanceSeries,
Expand Down
3 changes: 3 additions & 0 deletions src/components/charts/BalanceHistoryChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type BalanceHistoryChartProps = {
showLoaderWhenNoSnapshots?: boolean;
renderZeroBalanceWhenNoSnapshots?: boolean;
isBalanceChartDataReadyToQuery?: boolean;
preserveVisibleSeriesWhileNotReady?: boolean;
balanceOffset?: number;
onSelectedBalanceChange?: (balance?: number) => void;
preChartContent?: React.ReactNode;
Expand Down Expand Up @@ -89,6 +90,7 @@ const BalanceHistoryChart = ({
showLoaderWhenNoSnapshots = false,
renderZeroBalanceWhenNoSnapshots = false,
isBalanceChartDataReadyToQuery = true,
preserveVisibleSeriesWhileNotReady = false,
balanceOffset = 0,
onSelectedBalanceChange,
preChartContent,
Expand Down Expand Up @@ -124,6 +126,7 @@ const BalanceHistoryChart = ({
showLoaderWhenNoSnapshots,
renderZeroBalanceWhenNoSnapshots,
isBalanceChartDataReadyToQuery,
preserveVisibleSeriesWhileNotReady,
t,
onSelectedBalanceChange,
onChangeRowData,
Expand Down
50 changes: 34 additions & 16 deletions src/components/charts/useBalanceChartDisplayModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type UseBalanceChartDisplayModelArgs = {
showLoaderWhenNoSnapshots: boolean;
renderZeroBalanceWhenNoSnapshots: boolean;
isBalanceChartDataReadyToQuery?: boolean;
preserveVisibleSeriesWhileNotReady?: boolean;
t: (key: string) => string;
onSelectedBalanceChange?: (balance?: number) => void;
onChangeRowData?: (data?: BalanceChartCallbackChangeRowData) => void;
Expand Down Expand Up @@ -210,6 +211,7 @@ export function useBalanceChartDisplayModel({
showLoaderWhenNoSnapshots,
renderZeroBalanceWhenNoSnapshots,
isBalanceChartDataReadyToQuery = true,
preserveVisibleSeriesWhileNotReady = false,
t,
onSelectedBalanceChange,
onChangeRowData,
Expand Down Expand Up @@ -382,13 +384,24 @@ export function useBalanceChartDisplayModel({

useEffect(() => {
if (!isBalanceChartDataReadyToQuery) {
const visibleOwner = visibleStateRef.current;
const canPreserveVisibleSeries =
preserveVisibleSeriesWhileNotReady &&
visibleOwner?.scopeId === scopeId &&
visibleOwner?.quoteCurrency === committedQueryQuoteCurrency;
const selectedTimeframeIsVisible =
canPreserveVisibleSeries &&
visibleOwner?.timeframe === selectedTimeframe;

activeRequestIdRef.current += 1;
pendingSelectedTimestampRef.current = undefined;
gestureStartedRef.current = false;
lastHapticPointTsRef.current = undefined;
setSelectedPoint(undefined);
setVisibleState(undefined);
setLoading(true);
if (!canPreserveVisibleSeries) {
setVisibleState(undefined);
}
setLoading(!selectedTimeframeIsVisible);
setError(undefined);
onSelectedBalanceChangeRef.current?.(undefined);
return;
Expand Down Expand Up @@ -502,6 +515,7 @@ export function useBalanceChartDisplayModel({
chartDataRevisionSig,
committedQueryQuoteCurrency,
isBalanceChartDataReadyToQuery,
preserveVisibleSeriesWhileNotReady,
queryRevisionKey,
renderZeroBalanceWhenNoSnapshots,
shouldWaitForHistoricalRates,
Expand Down Expand Up @@ -540,8 +554,10 @@ export function useBalanceChartDisplayModel({
onSelectedBalanceChangeRef.current?.(undefined);
}, [committedQueryQuoteCurrency, queryRevisionKey, scopeId]);

const canDisplayVisibleState =
isBalanceChartDataReadyToQuery || preserveVisibleSeriesWhileNotReady;
const activeVisibleState =
isBalanceChartDataReadyToQuery &&
canDisplayVisibleState &&
visibleState?.scopeId === scopeId &&
visibleState?.quoteCurrency === committedQueryQuoteCurrency
? visibleState
Expand All @@ -550,7 +566,12 @@ export function useBalanceChartDisplayModel({
const visibleTimeframe = activeVisibleState?.timeframe ?? selectedTimeframe;
const visibleQuoteCurrency =
activeVisibleState?.quoteCurrency ?? committedQueryQuoteCurrency;
const displayedSelectedPoint = isBalanceChartDataReadyToQuery
const canInteractWithVisibleSeries =
isBalanceChartDataReadyToQuery ||
(!!activeVisibleState &&
preserveVisibleSeriesWhileNotReady &&
activeVisibleState.timeframe === selectedTimeframe);
const displayedSelectedPoint = canInteractWithVisibleSeries
? selectedPoint
: undefined;

Expand Down Expand Up @@ -658,11 +679,8 @@ export function useBalanceChartDisplayModel({
);
}, [displayedAnalysisPoint, onDisplayedAnalysisPointChange]);

const hasRenderableSeries =
isBalanceChartDataReadyToQuery &&
(visibleSeries?.graphPoints?.length || 0) >= 2;
const shouldDelayPendingOverlay =
isBalanceChartDataReadyToQuery && loading && hasRenderableSeries;
const hasRenderableSeries = (visibleSeries?.graphPoints?.length || 0) >= 2;
const shouldDelayPendingOverlay = loading && hasRenderableSeries;

useEffect(() => {
if (!shouldDelayPendingOverlay) {
Expand All @@ -680,7 +698,7 @@ export function useBalanceChartDisplayModel({
}, [shouldDelayPendingOverlay]);

const shouldShowLoader =
!isBalanceChartDataReadyToQuery ||
(!isBalanceChartDataReadyToQuery && !hasRenderableSeries) ||
pendingOverlayVisible ||
(!hasRenderableSeries &&
(loading || (showLoaderWhenNoSnapshots && hasAnyWallets)));
Expand Down Expand Up @@ -710,7 +728,7 @@ export function useBalanceChartDisplayModel({
const onPointSelected = useCallback(
(point: GraphPoint) => {
if (
!isBalanceChartDataReadyToQuery ||
!canInteractWithVisibleSeries ||
!visibleSeries ||
!gestureStartedRef.current
) {
Expand All @@ -733,7 +751,7 @@ export function useBalanceChartDisplayModel({
lastHapticPointTsRef.current = pointTs;
}
},
[balanceOffset, isBalanceChartDataReadyToQuery, visibleSeries],
[balanceOffset, canInteractWithVisibleSeries, visibleSeries],
);

const onTimeframeSelect = useCallback(
Expand All @@ -758,10 +776,10 @@ export function useBalanceChartDisplayModel({
visibleSeries,
visibleTimeframe,
visibleQuoteCurrency,
isLoading: loading || !isBalanceChartDataReadyToQuery,
pendingOverlayVisible: isBalanceChartDataReadyToQuery
? pendingOverlayVisible
: false,
isLoading:
loading ||
(!isBalanceChartDataReadyToQuery && !canInteractWithVisibleSeries),
pendingOverlayVisible,
shouldShowLoader,
error: isBalanceChartDataReadyToQuery ? error : undefined,
selectedPoint: displayedSelectedPoint,
Expand Down
8 changes: 7 additions & 1 deletion src/navigation/tabs/home/components/PortfolioBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ const PortfolioBalanceContent = () => {
shouldLeftAlignTopSection && persistedHomeChartCollapsed;
const showChartLoaderWhenNoSnapshots =
balanceChartReadiness.shouldShowChartLoader ||
(balanceChartsEnabled && !chartHasRenderableSeries);
(balanceChartsEnabled &&
!balanceChartReadiness.shouldPreserveStaleBalanceChart &&
!chartHasRenderableSeries);
const collapsedScale = 0.26;
const fullChartHeight =
chartBlockHeight || HOME_BALANCE_EXPANDED_CHART_HEIGHT;
Expand Down Expand Up @@ -399,6 +401,8 @@ const PortfolioBalanceContent = () => {
enabled: balanceChartsEnabled,
isBalanceChartDataReadyToQuery:
balanceChartReadiness.isBalanceChartDataReadyToQuery,
preserveChartDrivenStateWhileNotReady:
balanceChartReadiness.shouldPreserveStaleBalanceChart,
resetKey: chartLifecycleKey,
});
const commonBalanceHistoryChartProps: BalanceHistoryChartProps = {
Expand All @@ -412,6 +416,8 @@ const PortfolioBalanceContent = () => {
showLoaderWhenNoSnapshots: showChartLoaderWhenNoSnapshots,
isBalanceChartDataReadyToQuery:
balanceChartReadiness.isBalanceChartDataReadyToQuery,
preserveVisibleSeriesWhileNotReady:
balanceChartReadiness.shouldPreserveStaleBalanceChart,
// NOTE: Coinbase balance is intentionally excluded from the balance chart
// (Option B per product requirements) because we do not have historized
// Coinbase balance snapshots.
Expand Down
Loading
Loading