Skip to content

Commit

Permalink
fix(useBreadcrumbItems): avoid infinite recursion
Browse files Browse the repository at this point in the history
Fixes #148
  • Loading branch information
harlan-zw committed Dec 30, 2023
1 parent 0fa5e18 commit f6fd135
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { hasTrailingSlash, parseURL, stringifyParsedURL, withTrailingSlash, withoutTrailingSlash } from 'ufo'
import { withoutTrailingSlash } from 'ufo'
import type { RouteMeta } from 'vue-router'
import type { MaybeRefOrGetter } from 'vue'
import type { BreadcrumbLink } from '@nuxt/ui/dist/runtime/types'
import { pathBreadcrumbSegments } from '../../pure/breadcrumbs'
import {
computed,
defineBreadcrumb,
Expand Down Expand Up @@ -83,7 +84,6 @@ export function useBreadcrumbItems(options: BreadcrumbProps = {}) {
}
const current = withoutQuery(withoutTrailingSlash(toValue(options.path || useRoute().path) || rootNode))
return pathBreadcrumbSegments(current, rootNode)
.reverse()
.map(path => ({
to: path,
}) as BreadcrumbItemProps)
Expand Down Expand Up @@ -143,26 +143,3 @@ export function useBreadcrumbItems(options: BreadcrumbProps = {}) {
}
return items
}

export function pathBreadcrumbSegments(path: string, rootNode: string = '/') {
const startNode = parseURL(path)
const appendsTrailingSlash = hasTrailingSlash(startNode.pathname)

const stepNode = (node: ReturnType<typeof parseURL>, nodes: string[] = []) => {
const fullPath = stringifyParsedURL(node)
// the pathname will always be without the trailing slash
const currentPathName = node.pathname || '/'
// when we hit the root the path will be an empty string; we swap it out for a slash
nodes.push(fullPath || '/')
if (currentPathName !== rootNode) {
// strip the last path segment (/my/cool/path -> /my/cool)
node.pathname = currentPathName.substring(0, currentPathName.lastIndexOf('/'))
// if the input was provided with a trailing slash we need to honour that
if (appendsTrailingSlash)
node.pathname = withTrailingSlash(node.pathname.substring(0, node.pathname.lastIndexOf('/')))
stepNode(node, nodes)
}
return nodes
}
return stepNode(startNode)
}
25 changes: 25 additions & 0 deletions module/src/runtime/pure/breadcrumbs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { hasTrailingSlash, parseURL, stringifyParsedURL, withTrailingSlash } from 'ufo'

export function pathBreadcrumbSegments(path: string, rootNode: string = '/'): string[] {
const startNode = parseURL(path)
const appendsTrailingSlash = hasTrailingSlash(startNode.pathname)

const stepNode = (node: ReturnType<typeof parseURL>, nodes: string[] = []) => {
const fullPath = stringifyParsedURL(node)
// the pathname will always be without the trailing slash
const currentPathName = node.pathname || '/'
// when we hit the root the path will be an empty string; we swap it out for a slash
nodes.push(fullPath || '/')
if (currentPathName !== rootNode && currentPathName !== '/') {
// strip the last path segment (/my/cool/path -> /my/cool)
node.pathname = currentPathName.substring(0, currentPathName.lastIndexOf('/'))
// if the input was provided with a trailing slash we need to honour that
if (appendsTrailingSlash)
node.pathname = withTrailingSlash(node.pathname.substring(0, node.pathname.lastIndexOf('/')))
stepNode(node, nodes)
}
return nodes
}
return stepNode(startNode)
.reverse()
}
44 changes: 44 additions & 0 deletions test/unit/breadcrumbs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest'
import { pathBreadcrumbSegments } from '../../module/src/runtime/pure/breadcrumbs'

describe('breadcrumbs', () => {
it('basic', async () => {
const items = pathBreadcrumbSegments('/x/y', '/')
expect(items).toMatchInlineSnapshot(`
[
"/",
"/x",
"/x/y",
]
`)
})
it('empty root node', async () => {
const items = pathBreadcrumbSegments('/x/y', '')
expect(items).toMatchInlineSnapshot(`
[
"/",
"/x",
"/x/y",
]
`)
})
it('custom root node', async () => {
const items = pathBreadcrumbSegments('/x/y', '/x')
expect(items).toMatchInlineSnapshot(`
[
"/x",
"/x/y",
]
`)
})
it('broken root node', async () => {
const items = pathBreadcrumbSegments('/x/y', '/foo')
expect(items).toMatchInlineSnapshot(`
[
"/",
"/x",
"/x/y",
]
`)
})
})

0 comments on commit f6fd135

Please sign in to comment.