mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
refactor(web): migrate core toast call sites to base ui toast (#33643)
This commit is contained in:
@ -1,22 +1,16 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { toast, ToastHost } from '@/app/components/base/ui/toast'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||
import { fetchSubscriptionUrls } from '@/service/billing'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import Toast from '../../../../../base/toast'
|
||||
import { ALL_PLANS } from '../../../../config'
|
||||
import { Plan } from '../../../../type'
|
||||
import { PlanRange } from '../../../plan-switcher/plan-range-switcher'
|
||||
import CloudPlanItem from '../index'
|
||||
|
||||
vi.mock('../../../../../base/toast', () => ({
|
||||
default: {
|
||||
notify: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
}))
|
||||
@ -47,11 +41,19 @@ const mockUseAppContext = useAppContext as Mock
|
||||
const mockUseAsyncWindowOpen = useAsyncWindowOpen as Mock
|
||||
const mockBillingInvoices = consoleClient.billing.invoices as Mock
|
||||
const mockFetchSubscriptionUrls = fetchSubscriptionUrls as Mock
|
||||
const mockToastNotify = Toast.notify as Mock
|
||||
|
||||
let assignedHref = ''
|
||||
const originalLocation = window.location
|
||||
|
||||
const renderWithToastHost = (ui: React.ReactNode) => {
|
||||
return render(
|
||||
<>
|
||||
<ToastHost timeout={0} />
|
||||
{ui}
|
||||
</>,
|
||||
)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
@ -68,6 +70,7 @@ beforeAll(() => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
toast.close()
|
||||
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: true })
|
||||
mockUseAsyncWindowOpen.mockReturnValue(vi.fn(async open => await open()))
|
||||
mockBillingInvoices.mockResolvedValue({ url: 'https://billing.example' })
|
||||
@ -163,7 +166,7 @@ describe('CloudPlanItem', () => {
|
||||
it('should show toast when non-manager tries to buy a plan', () => {
|
||||
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: false })
|
||||
|
||||
render(
|
||||
renderWithToastHost(
|
||||
<CloudPlanItem
|
||||
plan={Plan.professional}
|
||||
currentPlan={Plan.sandbox}
|
||||
@ -173,10 +176,7 @@ describe('CloudPlanItem', () => {
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'billing.plansCommon.startBuilding' }))
|
||||
expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'error',
|
||||
message: 'billing.buyPermissionDeniedTip',
|
||||
}))
|
||||
expect(screen.getByText('billing.buyPermissionDeniedTip')).toBeInTheDocument()
|
||||
expect(mockBillingInvoices).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
@ -4,11 +4,11 @@ import type { BasicPlan } from '../../../type'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||
import { fetchSubscriptionUrls } from '@/service/billing'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import Toast from '../../../../base/toast'
|
||||
import { ALL_PLANS } from '../../../config'
|
||||
import { Plan } from '../../../type'
|
||||
import { Professional, Sandbox, Team } from '../../assets'
|
||||
@ -66,10 +66,9 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
return
|
||||
|
||||
if (!isCurrentWorkspaceManager) {
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'error',
|
||||
message: t('buyPermissionDeniedTip', { ns: 'billing' }),
|
||||
className: 'z-[1001]',
|
||||
title: t('buyPermissionDeniedTip', { ns: 'billing' }),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -83,7 +82,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
throw new Error('Failed to open billing page')
|
||||
}, {
|
||||
onError: (err) => {
|
||||
Toast.notify({ type: 'error', message: err.message || String(err) })
|
||||
toast.add({ type: 'error', title: err.message || String(err) })
|
||||
},
|
||||
})
|
||||
return
|
||||
@ -111,34 +110,34 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
{
|
||||
isMostPopularPlan && (
|
||||
<div className="flex items-center justify-center bg-saas-dify-blue-static px-1.5 py-1">
|
||||
<span className="system-2xs-semibold-uppercase text-text-primary-on-surface">
|
||||
<span className="text-text-primary-on-surface system-2xs-semibold-uppercase">
|
||||
{t('plansCommon.mostPopular', { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="system-sm-regular text-text-secondary">{t(`${i18nPrefix}.description`, { ns: 'billing' })}</div>
|
||||
<div className="text-text-secondary system-sm-regular">{t(`${i18nPrefix}.description`, { ns: 'billing' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Price */}
|
||||
<div className="flex items-end gap-x-2 px-1 pb-8 pt-4">
|
||||
{isFreePlan && (
|
||||
<span className="title-4xl-semi-bold text-text-primary">{t('plansCommon.free', { ns: 'billing' })}</span>
|
||||
<span className="text-text-primary title-4xl-semi-bold">{t('plansCommon.free', { ns: 'billing' })}</span>
|
||||
)}
|
||||
{!isFreePlan && (
|
||||
<>
|
||||
{isYear && (
|
||||
<span className="title-4xl-semi-bold text-text-quaternary line-through">
|
||||
<span className="text-text-quaternary line-through title-4xl-semi-bold">
|
||||
$
|
||||
{planInfo.price * 12}
|
||||
</span>
|
||||
)}
|
||||
<span className="title-4xl-semi-bold text-text-primary">
|
||||
<span className="text-text-primary title-4xl-semi-bold">
|
||||
$
|
||||
{isYear ? planInfo.price * 10 : planInfo.price}
|
||||
</span>
|
||||
<span className="system-md-regular pb-0.5 text-text-tertiary">
|
||||
<span className="pb-0.5 text-text-tertiary system-md-regular">
|
||||
{t('plansCommon.priceTip', { ns: 'billing' })}
|
||||
{t(`plansCommon.${!isYear ? 'month' : 'year'}`, { ns: 'billing' })}
|
||||
</span>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { toast, ToastHost } from '@/app/components/base/ui/toast'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import Toast from '../../../../../base/toast'
|
||||
import { contactSalesUrl, getStartedWithCommunityUrl, getWithPremiumUrl } from '../../../../config'
|
||||
import { SelfHostedPlan } from '../../../../type'
|
||||
import SelfHostedPlanItem from '../index'
|
||||
@ -16,12 +16,6 @@ vi.mock('../list', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../../base/toast', () => ({
|
||||
default: {
|
||||
notify: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
}))
|
||||
@ -35,11 +29,19 @@ vi.mock('../../../assets', () => ({
|
||||
}))
|
||||
|
||||
const mockUseAppContext = useAppContext as Mock
|
||||
const mockToastNotify = Toast.notify as Mock
|
||||
|
||||
let assignedHref = ''
|
||||
const originalLocation = window.location
|
||||
|
||||
const renderWithToastHost = (ui: React.ReactNode) => {
|
||||
return render(
|
||||
<>
|
||||
<ToastHost timeout={0} />
|
||||
{ui}
|
||||
</>,
|
||||
)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
@ -56,6 +58,7 @@ beforeAll(() => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
toast.close()
|
||||
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: true })
|
||||
assignedHref = ''
|
||||
})
|
||||
@ -90,13 +93,10 @@ describe('SelfHostedPlanItem', () => {
|
||||
it('should show toast when non-manager tries to proceed', () => {
|
||||
mockUseAppContext.mockReturnValue({ isCurrentWorkspaceManager: false })
|
||||
|
||||
render(<SelfHostedPlanItem plan={SelfHostedPlan.premium} />)
|
||||
renderWithToastHost(<SelfHostedPlanItem plan={SelfHostedPlan.premium} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /billing\.plans\.premium\.btnText/ }))
|
||||
|
||||
expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'error',
|
||||
message: 'billing.buyPermissionDeniedTip',
|
||||
}))
|
||||
expect(screen.getByText('billing.buyPermissionDeniedTip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should redirect to community url when community plan button clicked', () => {
|
||||
|
||||
@ -4,9 +4,9 @@ import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Azure, GoogleCloud } from '@/app/components/base/icons/src/public/billing'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Toast from '../../../../base/toast'
|
||||
import { contactSalesUrl, getStartedWithCommunityUrl, getWithPremiumUrl } from '../../../config'
|
||||
import { SelfHostedPlan } from '../../../type'
|
||||
import { Community, Enterprise, EnterpriseNoise, Premium, PremiumNoise } from '../../assets'
|
||||
@ -56,10 +56,9 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
const handleGetPayUrl = useCallback(() => {
|
||||
// Only workspace manager can buy plan
|
||||
if (!isCurrentWorkspaceManager) {
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'error',
|
||||
message: t('buyPermissionDeniedTip', { ns: 'billing' }),
|
||||
className: 'z-[1001]',
|
||||
title: t('buyPermissionDeniedTip', { ns: 'billing' }),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -82,18 +81,18 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
{/* Noise Effect */}
|
||||
{STYLE_MAP[plan].noise}
|
||||
<div className="flex flex-col px-5 py-4">
|
||||
<div className=" flex flex-col gap-y-6 px-1 pt-10">
|
||||
<div className="flex flex-col gap-y-6 px-1 pt-10">
|
||||
{STYLE_MAP[plan].icon}
|
||||
<div className="flex min-h-[104px] flex-col gap-y-2">
|
||||
<div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name`, { ns: 'billing' })}</div>
|
||||
<div className="system-md-regular line-clamp-2 text-text-secondary">{t(`${i18nPrefix}.description`, { ns: 'billing' })}</div>
|
||||
<div className="line-clamp-2 text-text-secondary system-md-regular">{t(`${i18nPrefix}.description`, { ns: 'billing' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Price */}
|
||||
<div className="flex items-end gap-x-2 px-1 pb-8 pt-4">
|
||||
<div className="title-4xl-semi-bold shrink-0 text-text-primary">{t(`${i18nPrefix}.price`, { ns: 'billing' })}</div>
|
||||
<div className="shrink-0 text-text-primary title-4xl-semi-bold">{t(`${i18nPrefix}.price`, { ns: 'billing' })}</div>
|
||||
{!isFreePlan && (
|
||||
<span className="system-md-regular pb-0.5 text-text-tertiary">
|
||||
<span className="pb-0.5 text-text-tertiary system-md-regular">
|
||||
{t(`${i18nPrefix}.priceTip`, { ns: 'billing' })}
|
||||
</span>
|
||||
)}
|
||||
@ -114,7 +113,7 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
<GoogleCloud />
|
||||
</div>
|
||||
</div>
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
<span className="text-text-tertiary system-xs-regular">
|
||||
{t('plans.premium.comingSoon', { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user