describe('UsageInfo', () => {
- it('renders the metric with a suffix unit and tooltip text', () => {
- render(
-
,
- )
+ describe('Default Mode (non-storage)', () => {
+ it('renders the metric with a suffix unit and tooltip text', () => {
+ render(
+
,
+ )
- expect(screen.getByTestId('usage-icon')).toBeInTheDocument()
- expect(screen.getByText('Apps')).toBeInTheDocument()
- expect(screen.getByText('30')).toBeInTheDocument()
- expect(screen.getByText('100')).toBeInTheDocument()
- expect(screen.getByText('GB')).toBeInTheDocument()
+ expect(screen.getByTestId('usage-icon')).toBeInTheDocument()
+ expect(screen.getByText('Apps')).toBeInTheDocument()
+ expect(screen.getByText('30')).toBeInTheDocument()
+ expect(screen.getByText('100')).toBeInTheDocument()
+ expect(screen.getByText('GB')).toBeInTheDocument()
+ })
+
+ it('renders inline unit when unitPosition is inline', () => {
+ render(
+
,
+ )
+
+ expect(screen.getByText('100GB')).toBeInTheDocument()
+ })
+
+ it('shows reset hint text instead of the unit when resetHint is provided', () => {
+ const resetHint = 'Resets in 3 days'
+ render(
+
,
+ )
+
+ expect(screen.getByText(resetHint)).toBeInTheDocument()
+ expect(screen.queryByText('GB')).not.toBeInTheDocument()
+ })
+
+ it('displays unlimited text when total is infinite', () => {
+ render(
+
,
+ )
+
+ expect(screen.getByText('billing.plansCommon.unlimited')).toBeInTheDocument()
+ })
+
+ it('applies warning color when usage is close to the limit', () => {
+ render(
+
,
+ )
+
+ const progressBar = screen.getByTestId('billing-progress-bar')
+ expect(progressBar).toHaveClass('bg-components-progress-warning-progress')
+ })
+
+ it('applies error color when usage exceeds the limit', () => {
+ render(
+
,
+ )
+
+ const progressBar = screen.getByTestId('billing-progress-bar')
+ expect(progressBar).toHaveClass('bg-components-progress-error-progress')
+ })
+
+ it('does not render the icon when hideIcon is true', () => {
+ render(
+
,
+ )
+
+ expect(screen.queryByTestId('usage-icon')).not.toBeInTheDocument()
+ })
})
- it('renders inline unit when unitPosition is inline', () => {
- render(
-
,
- )
+ describe('Storage Mode', () => {
+ describe('Below Threshold', () => {
+ it('should render indeterminate progress bar when usage is below threshold', () => {
+ render(
+
,
+ )
- expect(screen.getByText('100GB')).toBeInTheDocument()
- })
+ expect(screen.getByTestId('billing-progress-bar-indeterminate')).toBeInTheDocument()
+ expect(screen.queryByTestId('billing-progress-bar')).not.toBeInTheDocument()
+ })
- it('shows reset hint text instead of the unit when resetHint is provided', () => {
- const resetHint = 'Resets in 3 days'
- render(
-
,
- )
+ it('should display "< threshold" format when usage is below threshold (non-sandbox)', () => {
+ render(
+
,
+ )
- expect(screen.getByText(resetHint)).toBeInTheDocument()
- expect(screen.queryByText('GB')).not.toBeInTheDocument()
- })
+ // Text "< 50" is rendered inside a single span
+ expect(screen.getByText(/< 50/)).toBeInTheDocument()
+ expect(screen.getByText('5120MB')).toBeInTheDocument()
+ })
- it('displays unlimited text when total is infinite', () => {
- render(
-
,
- )
+ it('should display "< threshold unit" format when usage is below threshold (sandbox)', () => {
+ render(
+
,
+ )
- expect(screen.getByText('billing.plansCommon.unlimited')).toBeInTheDocument()
- })
+ // Text "< 50" is rendered inside a single span
+ expect(screen.getByText(/< 50/)).toBeInTheDocument()
+ // Unit "MB" appears in the display
+ expect(screen.getAllByText('MB').length).toBeGreaterThanOrEqual(1)
+ })
- it('applies warning color when usage is close to the limit', () => {
- render(
-
,
- )
+ it('should render full-width indeterminate bar for sandbox users below threshold', () => {
+ render(
+
,
+ )
- const progressBar = screen.getByTestId('billing-progress-bar')
- expect(progressBar).toHaveClass('bg-components-progress-warning-progress')
- })
+ const bar = screen.getByTestId('billing-progress-bar-indeterminate')
+ expect(bar).toHaveClass('w-full')
+ })
- it('applies error color when usage exceeds the limit', () => {
- render(
-
,
- )
+ it('should render narrow indeterminate bar for non-sandbox users below threshold', () => {
+ render(
+
,
+ )
- const progressBar = screen.getByTestId('billing-progress-bar')
- expect(progressBar).toHaveClass('bg-components-progress-error-progress')
- })
+ const bar = screen.getByTestId('billing-progress-bar-indeterminate')
+ expect(bar).toHaveClass('w-[30px]')
+ })
+ })
- it('does not render the icon when hideIcon is true', () => {
- render(
-
,
- )
+ describe('Sandbox Full Capacity', () => {
+ it('should render error color progress bar when sandbox usage >= threshold', () => {
+ render(
+
,
+ )
- expect(screen.queryByTestId('usage-icon')).not.toBeInTheDocument()
+ const progressBar = screen.getByTestId('billing-progress-bar')
+ expect(progressBar).toHaveClass('bg-components-progress-error-progress')
+ })
+
+ it('should display "threshold / threshold unit" format when sandbox is at full capacity', () => {
+ render(
+
,
+ )
+
+ // First span: "50", Third span: "50 MB"
+ expect(screen.getByText('50')).toBeInTheDocument()
+ expect(screen.getByText(/50 MB/)).toBeInTheDocument()
+ expect(screen.getByText('/')).toBeInTheDocument()
+ })
+ })
+
+ describe('Pro/Team Users Above Threshold', () => {
+ it('should render normal progress bar when usage >= threshold', () => {
+ render(
+
,
+ )
+
+ expect(screen.getByTestId('billing-progress-bar')).toBeInTheDocument()
+ expect(screen.queryByTestId('billing-progress-bar-indeterminate')).not.toBeInTheDocument()
+ })
+
+ it('should display actual usage when usage >= threshold', () => {
+ render(
+
,
+ )
+
+ expect(screen.getByText('100')).toBeInTheDocument()
+ expect(screen.getByText('5120MB')).toBeInTheDocument()
+ })
+ })
+
+ describe('Storage Tooltip', () => {
+ it('should render tooltip wrapper when storageTooltip is provided', () => {
+ const { container } = render(
+
,
+ )
+
+ // Tooltip wrapper should contain cursor-default class
+ const tooltipWrapper = container.querySelector('.cursor-default')
+ expect(tooltipWrapper).toBeInTheDocument()
+ })
+ })
})
})
diff --git a/web/app/components/billing/usage-info/index.tsx b/web/app/components/billing/usage-info/index.tsx
index 8f0c1bcbcc..f820b85eab 100644
--- a/web/app/components/billing/usage-info/index.tsx
+++ b/web/app/components/billing/usage-info/index.tsx
@@ -1,5 +1,5 @@
'use client'
-import type { FC } from 'react'
+import type { ComponentType, FC } from 'react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
@@ -9,7 +9,7 @@ import ProgressBar from '../progress-bar'
type Props = {
className?: string
- Icon: any
+ Icon: ComponentType<{ className?: string }>
name: string
tooltip?: string
usage: number
@@ -19,6 +19,11 @@ type Props = {
resetHint?: string
resetInDays?: number
hideIcon?: boolean
+ // Props for the 50MB threshold display logic
+ storageMode?: boolean
+ storageThreshold?: number
+ storageTooltip?: string
+ isSandboxPlan?: boolean
}
const WARNING_THRESHOLD = 80
@@ -35,30 +40,141 @@ const UsageInfo: FC
= ({
resetHint,
resetInDays,
hideIcon = false,
+ storageMode = false,
+ storageThreshold = 50,
+ storageTooltip,
+ isSandboxPlan = false,
}) => {
const { t } = useTranslation()
+ // Special display logic for usage below threshold (only in storage mode)
+ const isBelowThreshold = storageMode && usage < storageThreshold
+ // Sandbox at full capacity (usage >= threshold and it's sandbox plan)
+ const isSandboxFull = storageMode && isSandboxPlan && usage >= storageThreshold
+
const percent = usage / total * 100
- const color = percent >= 100
- ? 'bg-components-progress-error-progress'
- : (percent >= WARNING_THRESHOLD ? 'bg-components-progress-warning-progress' : 'bg-components-progress-bar-progress-solid')
+ const getProgressColor = () => {
+ if (percent >= 100)
+ return 'bg-components-progress-error-progress'
+ if (percent >= WARNING_THRESHOLD)
+ return 'bg-components-progress-warning-progress'
+ return 'bg-components-progress-bar-progress-solid'
+ }
+ const color = getProgressColor()
const isUnlimited = total === NUM_INFINITE
let totalDisplay: string | number = isUnlimited ? t('plansCommon.unlimited', { ns: 'billing' }) : total
if (!isUnlimited && unit && unitPosition === 'inline')
totalDisplay = `${total}${unit}`
const showUnit = !!unit && !isUnlimited && unitPosition === 'suffix'
const resetText = resetHint ?? (typeof resetInDays === 'number' ? t('usagePage.resetsIn', { ns: 'billing', count: resetInDays }) : undefined)
- const rightInfo = resetText
- ? (
+
+ const renderRightInfo = () => {
+ if (resetText) {
+ return (
{resetText}
)
- : (showUnit && (
+ }
+ if (showUnit) {
+ return (
{unit}
- ))
+ )
+ }
+ return null
+ }
+
+ // Render usage display
+ const renderUsageDisplay = () => {
+ // Storage mode: special display logic
+ if (storageMode) {
+ // Sandbox user at full capacity
+ if (isSandboxFull) {
+ return (
+
+
+ {storageThreshold}
+
+ /
+
+ {storageThreshold}
+ {' '}
+ {unit}
+
+
+ )
+ }
+ // Usage below threshold - show "< 50 MB" or "< 50 / 5GB"
+ if (isBelowThreshold) {
+ return (
+
+
+ <
+ {' '}
+ {storageThreshold}
+
+ {!isSandboxPlan && (
+ <>
+ /
+ {totalDisplay}
+ >
+ )}
+ {isSandboxPlan && {unit}}
+
+ )
+ }
+ // Pro/Team users with usage >= threshold - show actual usage
+ return (
+
+ {usage}
+ /
+ {totalDisplay}
+
+ )
+ }
+
+ // Default display (storageMode = false)
+ return (
+
+ {usage}
+ /
+ {totalDisplay}
+
+ )
+ }
+
+ const renderWithTooltip = (children: React.ReactNode) => {
+ if (storageMode && storageTooltip) {
+ return (
+ {storageTooltip}
+
+ )
+ }
+ return children
+ }
+
+ // Render progress bar with optional tooltip wrapper
+ const renderProgressBar = () => {
+ const progressBar = (
+
+ )
+ return renderWithTooltip(progressBar)
+ }
+
+ const renderUsageWithTooltip = () => {
+ return renderWithTooltip(renderUsageDisplay())
+ }
return (