Compare commits
2 Commits
codex/refa
...
codex/inte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9748fea0ea | ||
|
|
cd38e9f520 |
@@ -0,0 +1,54 @@
|
||||
import type { AnchorHTMLAttributes, ReactNode } from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { IntegrationSidebarNavItem } from '../sidebar-nav-item'
|
||||
|
||||
vi.mock('@/next/link', () => ({
|
||||
default: ({
|
||||
children,
|
||||
href,
|
||||
prefetch,
|
||||
...props
|
||||
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||
children?: ReactNode
|
||||
href: string
|
||||
prefetch?: boolean | null
|
||||
}) => (
|
||||
<a href={href} data-prefetch={prefetch === null ? 'auto' : prefetch} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}))
|
||||
|
||||
const item = {
|
||||
icon: 'i-ri-database-2-line',
|
||||
label: 'Data source',
|
||||
section: 'data-source' as const,
|
||||
}
|
||||
|
||||
describe('IntegrationSidebarNavItem', () => {
|
||||
it('enables route prefetch after pointer intent', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<IntegrationSidebarNavItem item={item} section="provider" />)
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Data source' })
|
||||
expect(link).toHaveAttribute('data-prefetch', 'false')
|
||||
|
||||
await user.hover(link)
|
||||
|
||||
expect(link).toHaveAttribute('data-prefetch', 'auto')
|
||||
})
|
||||
|
||||
it('enables route prefetch after keyboard focus', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<IntegrationSidebarNavItem item={item} section="provider" />)
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Data source' })
|
||||
expect(link).toHaveAttribute('data-prefetch', 'false')
|
||||
|
||||
await user.tab()
|
||||
|
||||
expect(link).toHaveFocus()
|
||||
expect(link).toHaveAttribute('data-prefetch', 'auto')
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ComponentType } from 'react'
|
||||
import type { IntegrationSection } from '@/app/components/integrations/routes'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useState } from 'react'
|
||||
import Link from '@/next/link'
|
||||
import { buildIntegrationPath } from './routes'
|
||||
import {
|
||||
@@ -55,6 +56,7 @@ export function IntegrationSidebarNavItem({
|
||||
onSelect,
|
||||
section,
|
||||
}: IntegrationSidebarNavItemProps) {
|
||||
const [isPrefetchEnabled, setIsPrefetchEnabled] = useState(false)
|
||||
const isActive = item.section === section
|
||||
const icon = isActive && item.activeIcon ? item.activeIcon : item.icon
|
||||
|
||||
@@ -119,6 +121,9 @@ export function IntegrationSidebarNavItem({
|
||||
return (
|
||||
<Link
|
||||
href={buildIntegrationPath(item.section)}
|
||||
prefetch={isPrefetchEnabled ? null : false}
|
||||
onMouseEnter={() => setIsPrefetchEnabled(true)}
|
||||
onFocus={() => setIsPrefetchEnabled(true)}
|
||||
aria-label={item.label}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
className={className}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { AnchorHTMLAttributes, ReactNode } from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import MainNavLink from '../nav-link'
|
||||
|
||||
vi.mock('@/next/link', () => ({
|
||||
default: ({
|
||||
children,
|
||||
href,
|
||||
prefetch,
|
||||
...props
|
||||
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||
children?: ReactNode
|
||||
href: string
|
||||
prefetch?: boolean | null
|
||||
}) => (
|
||||
<a href={href} data-prefetch={prefetch === null ? 'auto' : prefetch} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}))
|
||||
|
||||
const integrationsItem = {
|
||||
href: '/integrations/model-provider',
|
||||
label: 'Integrations',
|
||||
active: () => false,
|
||||
icon: 'i-ri-plug-line',
|
||||
activeIcon: 'i-ri-plug-fill',
|
||||
prefetchOnIntent: true,
|
||||
}
|
||||
|
||||
describe('MainNavLink', () => {
|
||||
it('enables Integration route prefetch after pointer intent', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<MainNavLink item={integrationsItem} pathname="/apps" />)
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Integrations' })
|
||||
expect(link).toHaveAttribute('data-prefetch', 'false')
|
||||
|
||||
await user.hover(link)
|
||||
|
||||
expect(link).toHaveAttribute('data-prefetch', 'auto')
|
||||
})
|
||||
|
||||
it('enables Integration route prefetch after keyboard focus', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<MainNavLink item={integrationsItem} pathname="/apps" />)
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Integrations' })
|
||||
expect(link).toHaveAttribute('data-prefetch', 'false')
|
||||
|
||||
await user.tab()
|
||||
|
||||
expect(link).toHaveFocus()
|
||||
expect(link).toHaveAttribute('data-prefetch', 'auto')
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { MainNavItem } from '../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useState } from 'react'
|
||||
import Link from '@/next/link'
|
||||
|
||||
const NavIcon = ({ icon, className }: { icon: string; className?: string }) => (
|
||||
@@ -16,11 +17,15 @@ type MainNavLinkProps = {
|
||||
}
|
||||
|
||||
const MainNavLink = ({ item, pathname, children }: MainNavLinkProps) => {
|
||||
const [isPrefetchEnabled, setIsPrefetchEnabled] = useState(false)
|
||||
const activated = item.active(pathname)
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={item.href}
|
||||
prefetch={item.prefetchOnIntent ? (isPrefetchEnabled ? null : false) : undefined}
|
||||
onMouseEnter={item.prefetchOnIntent ? () => setIsPrefetchEnabled(true) : undefined}
|
||||
onFocus={item.prefetchOnIntent ? () => setIsPrefetchEnabled(true) : undefined}
|
||||
aria-current={activated ? 'page' : undefined}
|
||||
title={item.label}
|
||||
className={cn(
|
||||
|
||||
@@ -60,6 +60,7 @@ export function MainNav({ className }: MainNavProps) {
|
||||
active: route.active,
|
||||
icon: route.icon,
|
||||
activeIcon: route.activeIcon,
|
||||
prefetchOnIntent: 'prefetchOnIntent' in route && route.prefetchOnIntent,
|
||||
})),
|
||||
[
|
||||
agentV2Enabled,
|
||||
|
||||
@@ -14,6 +14,7 @@ export type MainNavRouteConfig = {
|
||||
activeIcon: string
|
||||
visibility: MainNavRouteVisibility
|
||||
feature?: 'agentV2' | 'marketplace'
|
||||
prefetchOnIntent?: boolean
|
||||
} & ({ label: string; labelKey?: never } | { label?: never; labelKey: string })
|
||||
|
||||
export type MainNavRouteVisibilityOptions = {
|
||||
@@ -87,6 +88,7 @@ export const MAIN_NAV_ROUTES = [
|
||||
icon: 'i-custom-vender-main-nav-integrations',
|
||||
activeIcon: 'i-custom-vender-main-nav-integrations-active',
|
||||
visibility: VISIBLE_TO_ALL,
|
||||
prefetchOnIntent: true,
|
||||
},
|
||||
{
|
||||
key: 'marketplace',
|
||||
|
||||
@@ -8,4 +8,5 @@ export type MainNavItem = {
|
||||
active: (pathname: string) => boolean
|
||||
icon: string
|
||||
activeIcon: string
|
||||
prefetchOnIntent?: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user