Repository: kyeotic/raviger
Pull request #101 (2021-08-17 10:53:37)

fix(): ActiveLink classes
#99


Patch:
diff --git a/src/Link.tsx b/src/Link.tsx
index 09b8ce5..a866850 100644
--- a/src/Link.tsx
+++ b/src/Link.tsx
@@ -1,7 +1,7 @@
 import React, { useCallback, forwardRef, Ref } from 'react'
 
 import { navigate } from './navigate'
-import { usePath, useBasePath } from './path'
+import { useBasePath, useFullPath } from './path'
 
 export interface LinkProps
   extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
@@ -52,19 +52,29 @@ const RefLink = forwardRef<LinkRef, LinkProps>(Link) as (
 export default RefLink
 export { RefLink as Link }
 
-function ActiveLink(props: ActiveLinkProps, ref?: Ref<HTMLAnchorElement>) {
-  const basePath = useBasePath()
-  const path = usePath(basePath)
-  const href = getLinkHref(props.href, basePath)
-  // eslint-disable-next-line prefer-const
-  let { className, activeClass, exactActiveClass, ...rest } = props
+function ActiveLink(
+  {
+    basePath,
+    className,
+    exactActiveClass,
+    activeClass,
+    ...props
+  }: ActiveLinkProps,
+  ref?: Ref<HTMLAnchorElement>
+) {
+  const contextBasePath = useBasePath()
+  basePath = basePath || contextBasePath
+  const fullPath = useFullPath()
+
+  let { href } = props
+  href = absolutePathName(getLinkHref(href, basePath))
 
-  if (!className) className = ''
-  if (exactActiveClass && path === href) className += ` ${exactActiveClass}`
-  if (activeClass && (path ?? '').startsWith(href))
-    className += ` ${activeClass}`
+  if (exactActiveClass && fullPath === href) className += ` ${exactActiveClass}`
+  if (activeClass && fullPath.startsWith(href)) className += ` ${activeClass}`
 
-  return <RefLink {...rest} className={className} ref={ref} />
+  return (
+    <RefLink {...props} basePath={basePath} className={className} ref={ref} />
+  )
 }
 
 const ActiveLinkRef = forwardRef<LinkRef, ActiveLinkProps>(ActiveLink) as (
@@ -74,7 +84,12 @@ const ActiveLinkRef = forwardRef<LinkRef, ActiveLinkProps>(ActiveLink) as (
 export { ActiveLinkRef as ActiveLink }
 
 function getLinkHref(href: string, basePath = '') {
-  return href.substring(0, 1) === '/' ? basePath + href : href
+  return href.startsWith('/') ? basePath + href : href
+}
+
+function absolutePathName(href: string): string {
+  if (href.startsWith('/')) return href
+  return new URL(href, document.baseURI).pathname
 }
 
 function shouldTrap(


Test patch:
diff --git a/test/Link.spec.tsx b/test/Link.spec.tsx
index f335058..52405f2 100644
--- a/test/Link.spec.tsx
+++ b/test/Link.spec.tsx
@@ -120,4 +120,53 @@ describe('ActiveLink', () => {
     expect(getByTestId('link')).toHaveClass('extra')
     expect(getByTestId('link')).not.toHaveClass('double')
   })
+
+  test('navigates to href with basePath', async () => {
+    act(() => navigate('/'))
+    const { getByTestId } = render(
+      <ActiveLink
+        href="/foo"
+        basePath="/bar"
+        className="base"
+        activeClass="active"
+        exactActiveClass="exact"
+        data-testid="link"
+      >
+        go to foo
+      </ActiveLink>
+    )
+
+    expect(getByTestId('link')).not.toHaveClass('active')
+    expect(getByTestId('link')).not.toHaveClass('exact')
+
+    act(() => void fireEvent.click(getByTestId('link')))
+    expect(document.location.pathname).toEqual('/bar/foo')
+
+    expect(getByTestId('link')).toHaveClass('active')
+    expect(getByTestId('link')).toHaveClass('exact')
+  })
+
+  test('href without leading slash', async () => {
+    act(() => navigate('/bar/foo'))
+    const { getByTestId } = render(
+      <ActiveLink
+        href="test"
+        className="base"
+        activeClass="active"
+        exactActiveClass="exact"
+        data-testid="link"
+      >
+        go to foo
+      </ActiveLink>
+    )
+
+    expect(getByTestId('link')).not.toHaveClass('active')
+    expect(getByTestId('link')).not.toHaveClass('exact')
+
+    act(() => void fireEvent.click(getByTestId('link')))
+    expect(document.location.pathname).toEqual('/bar/test')
+
+    expect(getByTestId('link')).toHaveClass('active')
+    expect(getByTestId('link')).toHaveClass('exact')
+  })
 })