mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 18:35:07 +03:00
fix(studio): drop unused layout work from the replica sheet close bug
The overview gap was sheet close restoring focus, not chart h-full or a custom scroller.
This commit is contained in:
1 parent
c9bd30a42b
commit
ce830fae16
7 files changed
+138
-286
No files matched your search
@@ -93,7 +93,7 @@ const PERCENTAGE_Y_AXIS_PROPS = {
|
||||
|
||||
const getUsageCardClassName = (status: UsageMetricStatus) =>
|
||||
cn(
|
||||
'flex flex-col transition-colors',
|
||||
'h-full flex flex-col transition-colors',
|
||||
status === 'warning' && 'border-warning-400 bg-warning-200/30',
|
||||
status === 'negative' && 'border-destructive-400 bg-destructive-200/30'
|
||||
)
|
||||
@@ -160,14 +160,14 @@ export const ComputeAndDiskUsageCharts = ({ className }: { className?: string })
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'grid grid-cols-1 gap-4 @[680px]:grid-cols-2 @[680px]:items-stretch [overflow-anchor:none]',
|
||||
'grid grid-cols-1 gap-4 @[680px]:grid-cols-2 @[680px]:items-stretch',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div id="cpu" className="scroll-mt-24 min-h-0">
|
||||
<div id="cpu" className="scroll-mt-24 h-full min-h-0">
|
||||
<span id="ram" className="block h-0 scroll-mt-24" aria-hidden />
|
||||
<span id="disk_io" className="block h-0 scroll-mt-24" aria-hidden />
|
||||
<Chart isLoading={isLoading} isErrored={isError}>
|
||||
<Chart isLoading={isLoading} isErrored={isError} className="h-full">
|
||||
<ChartCard className={getUsageCardClassName(computeUsageStatus)}>
|
||||
<ChartHeader align="start" className="min-h-[5.5rem]">
|
||||
<ChartMetric
|
||||
@@ -245,8 +245,8 @@ export const ComputeAndDiskUsageCharts = ({ className }: { className?: string })
|
||||
</Chart>
|
||||
</div>
|
||||
|
||||
<div id="disk" className="scroll-mt-24 min-h-0">
|
||||
<Chart isLoading={isLoading} isErrored={isError}>
|
||||
<div id="disk" className="scroll-mt-24 h-full min-h-0">
|
||||
<Chart isLoading={isLoading} isErrored={isError} className="h-full">
|
||||
<ChartCard className={getUsageCardClassName(diskUsageStatus)}>
|
||||
<ChartHeader align="start" className="min-h-[5.5rem]">
|
||||
<ChartMetric
|
||||
|
||||
@@ -68,16 +68,7 @@ export function ComputeSection({
|
||||
<DocsButton href={`${DOCS_URL}/guides/platform/compute-and-disk`} />
|
||||
</PageSectionAside>
|
||||
</PageSectionMeta>
|
||||
<<<<<<< HEAD
|
||||
<PageSectionContent ref={settingsRef} className="scroll-mt-24">
|
||||
=======
|
||||
<PageSectionContent
|
||||
ref={settingsRef}
|
||||
id="compute"
|
||||
tabIndex={-1}
|
||||
className="scroll-mt-24 outline-hidden"
|
||||
>
|
||||
>>>>>>> 33f4960eb1 (fix(studio): stop the add-replica sheet restoring focus into the page)
|
||||
<PageSectionContent ref={settingsRef} id="compute" className="scroll-mt-24">
|
||||
<ComputeSizeField form={form} disabled={disabled} />
|
||||
</PageSectionContent>
|
||||
</PageSection>
|
||||
|
||||
@@ -28,7 +28,6 @@ import { AdvancedSection, ComputeSection, DiskSection } from './DiskManagementFo
|
||||
import { DiskMangementRestartRequiredSection } from './DiskManagementRestartRequiredSection'
|
||||
import { DiskManagementReviewAndSubmitDialog } from './DiskManagementReviewAndSubmitDialog/DiskManagementReviewAndSubmitDialog'
|
||||
import { useDiskManagementReviewChanges } from './DiskManagementReviewAndSubmitDialog/DiskManagementReviewAndSubmitDialog.hooks'
|
||||
import { scrollElementIntoContainer } from './scrollElementIntoContainer'
|
||||
import { BillingChangeBadge } from './ui/BillingChangeBadge'
|
||||
import {
|
||||
DISK_LIMITS,
|
||||
@@ -41,7 +40,6 @@ import {
|
||||
recommendComputeParser,
|
||||
subscribeRecommendCompute,
|
||||
} from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/recommendCompute'
|
||||
import { useMainScrollContainer } from '@/components/layouts/MainScrollContainerContext'
|
||||
import { PADDING_CLASSES } from '@/components/layouts/Scaffold'
|
||||
import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
|
||||
import {
|
||||
@@ -92,14 +90,6 @@ export function DiskManagementForm({
|
||||
const storageSettingsRef = useRef<HTMLDivElement>(null)
|
||||
const computeSettingsRef = useRef<HTMLDivElement>(null)
|
||||
const diskSizeSettingsRef = useRef<HTMLDivElement>(null)
|
||||
const mainScrollContainer = useMainScrollContainer()
|
||||
const mainScrollContainerRef = useRef(mainScrollContainer)
|
||||
mainScrollContainerRef.current = mainScrollContainer
|
||||
|
||||
const scrollToComputeSection = () => {
|
||||
scrollElementIntoContainer(computeSettingsRef.current, mainScrollContainerRef.current)
|
||||
computeSettingsRef.current?.focus({ preventScroll: true })
|
||||
}
|
||||
|
||||
const isSpendCapEnabled = org?.plan.id !== 'free' && !org?.usage_billing_enabled
|
||||
|
||||
@@ -402,7 +392,7 @@ export function DiskManagementForm({
|
||||
setRecommendCompute(null)
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
scrollToComputeSection()
|
||||
computeSettingsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}, RECOMMEND_COMPUTE_SCROLL_DELAY_MS)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
@@ -417,7 +407,7 @@ export function DiskManagementForm({
|
||||
})
|
||||
void form.trigger(['provisionedIOPS', 'throughput'])
|
||||
window.setTimeout(() => {
|
||||
scrollToComputeSection()
|
||||
computeSettingsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}, RECOMMEND_COMPUTE_SCROLL_DELAY_MS)
|
||||
})
|
||||
}, [form])
|
||||
@@ -428,7 +418,7 @@ export function DiskManagementForm({
|
||||
if (window.location.hash !== '#compute') return
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
scrollToComputeSection()
|
||||
computeSettingsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}, 100)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
@@ -458,7 +448,7 @@ export function DiskManagementForm({
|
||||
if (!scrollTarget) return
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
scrollElementIntoContainer(scrollTarget.current, mainScrollContainerRef.current)
|
||||
scrollTarget.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}, 100)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
@@ -466,151 +456,145 @@ export function DiskManagementForm({
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
{/*
|
||||
ProjectLayout's main is a viewport-tall flex column. If this form is a
|
||||
direct flex item, production builds resolve descendant h-full / flex-1
|
||||
against that main (next dev often does not). Keep a block wrapper so
|
||||
height stays content-sized.
|
||||
*/}
|
||||
<div className="w-full">
|
||||
<form id="disk-compute-form" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<PageContainer size="default" className="pb-16">
|
||||
<div className="pt-12">
|
||||
<form id="disk-compute-form" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<PageContainer size="default" className="pb-16">
|
||||
<PageSection>
|
||||
<PageSectionContent>
|
||||
{overviewExtra}
|
||||
<ComputeAndDiskUsageCharts className={cn(overviewExtra && 'mt-6', chartsClassName)} />
|
||||
</PageSectionContent>
|
||||
</PageSection>
|
||||
|
||||
{(isProjectResizing ||
|
||||
isProjectRequestingDiskChanges ||
|
||||
(isEntitlementsLoaded && !isPlanUpgradeRequired && noPermissions)) && (
|
||||
<div className="relative flex flex-col gap-10">
|
||||
<DiskMangementRestartRequiredSection
|
||||
visible={isProjectResizing}
|
||||
title="Your project will now automatically restart."
|
||||
description="Your project will be unavailable for up to 2 mins."
|
||||
/>
|
||||
<NoticeBar
|
||||
type="default"
|
||||
visible={isProjectRequestingDiskChanges}
|
||||
title="Disk configuration changes have been requested"
|
||||
description="The requested changes will be applied to your disk shortly"
|
||||
/>
|
||||
<NoticeBar
|
||||
type="default"
|
||||
visible={isEntitlementsLoaded && !isPlanUpgradeRequired && noPermissions}
|
||||
title="You do not have permission to update disk configuration"
|
||||
description="Please contact your organization administrator to update your disk configuration"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(isProjectResizing ||
|
||||
isProjectRequestingDiskChanges ||
|
||||
(isEntitlementsLoaded && !isPlanUpgradeRequired && noPermissions)) && (
|
||||
<div className="relative flex flex-col gap-10">
|
||||
<DiskMangementRestartRequiredSection
|
||||
visible={isProjectResizing}
|
||||
title="Your project will now automatically restart."
|
||||
description="Your project will be unavailable for up to 2 mins."
|
||||
/>
|
||||
<NoticeBar
|
||||
type="default"
|
||||
visible={isProjectRequestingDiskChanges}
|
||||
title="Disk configuration changes have been requested"
|
||||
description="The requested changes will be applied to your disk shortly"
|
||||
/>
|
||||
<NoticeBar
|
||||
type="default"
|
||||
visible={isEntitlementsLoaded && !isPlanUpgradeRequired && noPermissions}
|
||||
title="You do not have permission to update disk configuration"
|
||||
description="Please contact your organization administrator to update your disk configuration"
|
||||
/>
|
||||
</div>
|
||||
{beforeScaling}
|
||||
|
||||
<PageSection>
|
||||
<PageSectionMeta>
|
||||
<PageSectionSummary>
|
||||
<PageSectionTitle>Scaling</PageSectionTitle>
|
||||
</PageSectionSummary>
|
||||
</PageSectionMeta>
|
||||
{isEntitlementsLoaded && isPlanUpgradeRequired && (
|
||||
<UpgradeToPro
|
||||
featureProposition="configure compute and disk"
|
||||
primaryText="Only available on Pro Plan and above"
|
||||
secondaryText="Upgrade to the Pro Plan to configure compute and disk settings."
|
||||
/>
|
||||
)}
|
||||
<PageSectionContent>
|
||||
<ComputeSection
|
||||
form={form}
|
||||
settingsRef={computeSettingsRef}
|
||||
showBillingBadge={showComputeBillingBadge}
|
||||
beforePrice={Number(computeSizePrice.oldPrice)}
|
||||
afterPrice={Number(computeSizePrice.newPrice)}
|
||||
disabled={disableComputeInputs}
|
||||
/>
|
||||
|
||||
{beforeScaling}
|
||||
<DiskSection
|
||||
form={form}
|
||||
settingsRef={diskSizeSettingsRef}
|
||||
showBillingBadge={showDiskBillingBadge}
|
||||
beforePrice={Number(diskSizePrice.oldPrice)}
|
||||
afterPrice={Number(diskSizePrice.newPrice)}
|
||||
isAws={isAws}
|
||||
isAwsK8s={isAwsK8s}
|
||||
isBranch={isBranch}
|
||||
isNoticeVisible={isDiskNoticeVisible}
|
||||
isReadOnlyMode={!!isReadOnlyMode}
|
||||
usedPercentage={usedPercentage}
|
||||
isWithinCooldownWindow={isWithinCooldownWindow}
|
||||
currentDiskSizeGb={defaultValues.totalSize}
|
||||
disableDiskSizeInput={disableDiskSizeInput}
|
||||
/>
|
||||
|
||||
<PageSection>
|
||||
<PageSectionMeta>
|
||||
<PageSectionSummary>
|
||||
<PageSectionTitle>Scaling</PageSectionTitle>
|
||||
</PageSectionSummary>
|
||||
</PageSectionMeta>
|
||||
{isEntitlementsLoaded && isPlanUpgradeRequired && (
|
||||
<UpgradeToPro
|
||||
featureProposition="configure compute and disk"
|
||||
primaryText="Only available on Pro Plan and above"
|
||||
secondaryText="Upgrade to the Pro Plan to configure compute and disk settings."
|
||||
{isAws && (
|
||||
<AdvancedSection
|
||||
form={form}
|
||||
autoscaleSettingsRef={autoscaleSettingsRef}
|
||||
storageSettingsRef={storageSettingsRef}
|
||||
showBillingBadge={showAdvancedBillingBadge}
|
||||
beforePrice={advancedBeforePrice}
|
||||
afterPrice={advancedAfterPrice}
|
||||
disableIopsThroughputConfig={disableIopsThroughputConfig}
|
||||
canUpdateDiskConfiguration={canUpdateDiskConfiguration}
|
||||
isDiskTooSmallForCustomIops={isDiskTooSmallForCustomIops}
|
||||
disableDiskInputs={disableDiskInputs}
|
||||
disableDiskSizeInput={disableDiskSizeInput}
|
||||
suggestedDiskSizeForCustomIops={suggestedDiskSizeForCustomIops}
|
||||
/>
|
||||
)}
|
||||
<PageSectionContent>
|
||||
<ComputeSection
|
||||
form={form}
|
||||
settingsRef={computeSettingsRef}
|
||||
showBillingBadge={showComputeBillingBadge}
|
||||
beforePrice={Number(computeSizePrice.oldPrice)}
|
||||
afterPrice={Number(computeSizePrice.newPrice)}
|
||||
disabled={disableComputeInputs}
|
||||
/>
|
||||
</PageSectionContent>
|
||||
</PageSection>
|
||||
</PageContainer>
|
||||
|
||||
<DiskSection
|
||||
form={form}
|
||||
settingsRef={diskSizeSettingsRef}
|
||||
showBillingBadge={showDiskBillingBadge}
|
||||
beforePrice={Number(diskSizePrice.oldPrice)}
|
||||
afterPrice={Number(diskSizePrice.newPrice)}
|
||||
isAws={isAws}
|
||||
isAwsK8s={isAwsK8s}
|
||||
isBranch={isBranch}
|
||||
isNoticeVisible={isDiskNoticeVisible}
|
||||
isReadOnlyMode={!!isReadOnlyMode}
|
||||
usedPercentage={usedPercentage}
|
||||
isWithinCooldownWindow={isWithinCooldownWindow}
|
||||
currentDiskSizeGb={defaultValues.totalSize}
|
||||
disableDiskSizeInput={disableDiskSizeInput}
|
||||
/>
|
||||
|
||||
{isAws && (
|
||||
<AdvancedSection
|
||||
form={form}
|
||||
autoscaleSettingsRef={autoscaleSettingsRef}
|
||||
storageSettingsRef={storageSettingsRef}
|
||||
showBillingBadge={showAdvancedBillingBadge}
|
||||
beforePrice={advancedBeforePrice}
|
||||
afterPrice={advancedAfterPrice}
|
||||
disableIopsThroughputConfig={disableIopsThroughputConfig}
|
||||
canUpdateDiskConfiguration={canUpdateDiskConfiguration}
|
||||
isDiskTooSmallForCustomIops={isDiskTooSmallForCustomIops}
|
||||
disableDiskInputs={disableDiskInputs}
|
||||
disableDiskSizeInput={disableDiskSizeInput}
|
||||
suggestedDiskSizeForCustomIops={suggestedDiskSizeForCustomIops}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{isDirty ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 20 }}
|
||||
transition={{ duration: 0.1, delay: 0.2 }}
|
||||
className="z-10 w-full left-0 right-0 sticky bottom-0 bg-surface-100 border-t h-16 items-center flex"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'mx-auto w-full max-w-[1200px]',
|
||||
PADDING_CLASSES,
|
||||
'flex items-center justify-end gap-3'
|
||||
)}
|
||||
</PageSectionContent>
|
||||
</PageSection>
|
||||
</PageContainer>
|
||||
|
||||
<AnimatePresence>
|
||||
{isDirty ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 20 }}
|
||||
transition={{ duration: 0.1, delay: 0.2 }}
|
||||
className="z-10 w-full left-0 right-0 sticky bottom-0 bg-surface-100 border-t h-16 items-center flex"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'mx-auto w-full max-w-[1200px]',
|
||||
PADDING_CLASSES,
|
||||
'flex items-center justify-end gap-3'
|
||||
)}
|
||||
<BillingChangeBadge
|
||||
show={isDirty}
|
||||
beforePrice={totalBeforePrice}
|
||||
afterPrice={totalAfterPrice}
|
||||
/>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => form.reset()}
|
||||
disabled={!isDirty}
|
||||
size="medium"
|
||||
>
|
||||
<BillingChangeBadge
|
||||
show={isDirty}
|
||||
beforePrice={totalBeforePrice}
|
||||
afterPrice={totalAfterPrice}
|
||||
/>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => form.reset()}
|
||||
disabled={!isDirty}
|
||||
size="medium"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<DiskManagementReviewAndSubmitDialog
|
||||
loading={isUpdatingConfig}
|
||||
disabled={noPermissions}
|
||||
form={form}
|
||||
numReplicas={readReplicas.length}
|
||||
isDialogOpen={isDialogOpen}
|
||||
onSubmit={onSubmit}
|
||||
setIsDialogOpen={setIsDialogOpen}
|
||||
message={message}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
</div>
|
||||
Cancel
|
||||
</Button>
|
||||
<DiskManagementReviewAndSubmitDialog
|
||||
loading={isUpdatingConfig}
|
||||
disabled={noPermissions}
|
||||
form={form}
|
||||
numReplicas={readReplicas.length}
|
||||
isDialogOpen={isDialogOpen}
|
||||
onSubmit={onSubmit}
|
||||
setIsDialogOpen={setIsDialogOpen}
|
||||
message={message}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { scrollElementIntoContainer } from './scrollElementIntoContainer'
|
||||
|
||||
const mockRect = (top: number): DOMRect =>
|
||||
({
|
||||
top,
|
||||
bottom: top + 40,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: 0,
|
||||
height: 40,
|
||||
x: 0,
|
||||
y: top,
|
||||
toJSON: () => ({}),
|
||||
}) as DOMRect
|
||||
|
||||
describe('scrollElementIntoContainer', () => {
|
||||
afterEach(() => {
|
||||
document.body.replaceChildren()
|
||||
})
|
||||
|
||||
it('scrolls the overflow container by the element offset minus the top margin', () => {
|
||||
const container = document.createElement('div')
|
||||
const element = document.createElement('div')
|
||||
container.append(element)
|
||||
document.body.append(container)
|
||||
|
||||
Object.defineProperty(container, 'scrollTop', { configurable: true, value: 50 })
|
||||
vi.spyOn(container, 'getBoundingClientRect').mockReturnValue(mockRect(100))
|
||||
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(mockRect(500))
|
||||
const scrollTo = vi.fn()
|
||||
container.scrollTo = scrollTo
|
||||
|
||||
scrollElementIntoContainer(element, container, { offset: 96, behavior: 'auto' })
|
||||
|
||||
expect(scrollTo).toHaveBeenCalledWith({ top: 354, behavior: 'auto' })
|
||||
})
|
||||
|
||||
it('falls back to scrollIntoView when there is no overflow container', () => {
|
||||
const element = document.createElement('div')
|
||||
const scrollIntoView = vi.fn()
|
||||
element.scrollIntoView = scrollIntoView
|
||||
|
||||
scrollElementIntoContainer(element, null, { behavior: 'auto' })
|
||||
|
||||
expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'auto', block: 'start' })
|
||||
})
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
const DEFAULT_OFFSET_PX = 96 // matches scroll-mt-24
|
||||
|
||||
/**
|
||||
* Scroll `element` to the top of `container` without Element.scrollIntoView.
|
||||
* scrollIntoView walks every overflow ancestor (Studio has two nested mains)
|
||||
* and, mid layout shift, can inflate a flex item instead of moving scrollTop.
|
||||
*/
|
||||
export function scrollElementIntoContainer(
|
||||
element: HTMLElement | null | undefined,
|
||||
container: HTMLElement | null | undefined,
|
||||
{
|
||||
behavior = 'smooth',
|
||||
offset = DEFAULT_OFFSET_PX,
|
||||
}: { behavior?: ScrollBehavior; offset?: number } = {}
|
||||
) {
|
||||
if (!element) return
|
||||
|
||||
if (!container) {
|
||||
element.scrollIntoView({ behavior, block: 'start' })
|
||||
return
|
||||
}
|
||||
|
||||
const top =
|
||||
element.getBoundingClientRect().top -
|
||||
container.getBoundingClientRect().top +
|
||||
container.scrollTop -
|
||||
offset
|
||||
|
||||
container.scrollTo({ top: Math.max(0, top), behavior })
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ export const InfrastructureTopology = () => {
|
||||
if (!infrastructureReadReplicas) return null
|
||||
|
||||
return (
|
||||
<div className="w-full h-[400px] shrink-0 border border-muted rounded-md overflow-hidden relative [overflow-anchor:none]">
|
||||
<div className="w-full h-[400px] border border-muted rounded-md overflow-hidden flex flex-col relative">
|
||||
<InstanceConfiguration />
|
||||
</div>
|
||||
)
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
requestRecommendCompute,
|
||||
subscribeRecommendCompute,
|
||||
} from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/recommendCompute'
|
||||
|
||||
describe('recommendCompute bridge', () => {
|
||||
afterEach(() => {
|
||||
// Clear any leftover subscriber or queued recommendation between tests.
|
||||
subscribeRecommendCompute(() => {})()
|
||||
})
|
||||
|
||||
it('delivers the recommended size to the active subscriber', () => {
|
||||
const listener = vi.fn()
|
||||
subscribeRecommendCompute(listener)
|
||||
|
||||
requestRecommendCompute('ci_small')
|
||||
expect(listener).toHaveBeenCalledWith('ci_small')
|
||||
})
|
||||
|
||||
it('queues a recommendation until a subscriber mounts', () => {
|
||||
const listener = vi.fn()
|
||||
|
||||
requestRecommendCompute('ci_xlarge')
|
||||
expect(listener).not.toHaveBeenCalled()
|
||||
|
||||
subscribeRecommendCompute(listener)
|
||||
expect(listener).toHaveBeenCalledWith('ci_xlarge')
|
||||
})
|
||||
|
||||
it('replaces the previous subscriber', () => {
|
||||
const first = vi.fn()
|
||||
const second = vi.fn()
|
||||
|
||||
subscribeRecommendCompute(first)
|
||||
subscribeRecommendCompute(second)
|
||||
|
||||
requestRecommendCompute('ci_xlarge')
|
||||
|
||||
expect(first).not.toHaveBeenCalled()
|
||||
expect(second).toHaveBeenCalledWith('ci_xlarge')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user