-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9c21f29
commit a93329b
Showing
10 changed files
with
469 additions
and
89 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<template> | ||
<nav aria-label="Breadcrumb"> | ||
<transition-group class="pdap-breadcrumbs" name="pdap-breadcrumbs" tag="ul"> | ||
<li | ||
v-for="breadcrumb in breadcrumbs" | ||
:key="breadcrumb.text" | ||
:class="{ 'is-active': breadcrumb.active }" | ||
> | ||
<router-link v-if="!breadcrumb.active" :to="breadcrumb.path"> | ||
{{ breadcrumb.text }} | ||
</router-link> | ||
<span v-else>{{ breadcrumb.text }}</span> | ||
</li> | ||
</transition-group> | ||
</nav> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computed } from 'vue'; | ||
import { useRoute } from 'vue-router'; | ||
import { getBreadcrumbs } from '../../utils/breadcrumbs'; | ||
const route = useRoute(); | ||
const breadcrumbs = computed(() => getBreadcrumbs(route)); | ||
</script> | ||
|
||
<style scoped> | ||
.pdap-breadcrumbs { | ||
@apply flex items-center; | ||
} | ||
.pdap-breadcrumbs li { | ||
@apply flex items-center; | ||
} | ||
.pdap-breadcrumbs li:not(:first-child)::before { | ||
@apply mx-2; | ||
content: '/'; | ||
} | ||
.pdap-breadcrumbs .is-active { | ||
@apply text-neutral-950; | ||
} | ||
/* Animations */ | ||
.pdap-breadcrumbs-enter-active, | ||
.pdap-breadcrumbs-leave-active { | ||
transition: opacity 0.2s ease; | ||
} | ||
.pdap-breadcrumbs-enter-from, | ||
.pdap-breadcrumbs-leave-to { | ||
opacity: 0; | ||
} | ||
</style> |
30 changes: 30 additions & 0 deletions
30
src/components/Breadcrumbs/__snapshots__/breadcrumbs.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`PdapBreadcrumbs > renders breadcrumbs correctly for a double-nested route 1`] = ` | ||
<nav aria-label="Breadcrumb"> | ||
<transition-group-stub appear="false" class="pdap-breadcrumbs" css="true" name="pdap-breadcrumbs" persisted="false" tag="ul"> | ||
<li class> | ||
<a>Product</a> | ||
</li> | ||
<li class> | ||
<a>Product ID</a> | ||
</li> | ||
<li class="is-active"> | ||
<span>Edit Product ID</span> | ||
</li> | ||
</transition-group-stub> | ||
</nav> | ||
`; | ||
|
||
exports[`PdapBreadcrumbs > renders breadcrumbs correctly for a single-nested route 1`] = ` | ||
<nav aria-label="Breadcrumb"> | ||
<transition-group-stub appear="false" class="pdap-breadcrumbs" css="true" name="pdap-breadcrumbs" persisted="false" tag="ul"> | ||
<li class> | ||
<a>Product</a> | ||
</li> | ||
<li class="is-active"> | ||
<span>Product ID</span> | ||
</li> | ||
</transition-group-stub> | ||
</nav> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { RouterLinkStub, flushPromises, mount } from '@vue/test-utils'; | ||
import { createRouter, createWebHistory } from 'vue-router'; | ||
import PdapBreadcrumbs from './PdapBreadcrumbs.vue'; | ||
import { describe, it, expect } from 'vitest'; | ||
|
||
const routes = [ | ||
{ | ||
path: '/product', | ||
name: 'Product', | ||
meta: { breadcrumbText: 'Product' }, | ||
redirect: undefined, | ||
children: [ | ||
{ | ||
path: '/product/:id', | ||
name: 'Product ID', | ||
meta: { breadcrumbText: 'Product ID' }, | ||
redirect: undefined, | ||
children: [ | ||
{ | ||
path: '/product/:id/edit', | ||
name: 'Edit Product ID', | ||
meta: { breadcrumbText: 'Edit Product ID' }, | ||
redirect: undefined, | ||
children: [], | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
]; | ||
|
||
const router = createRouter({ | ||
history: createWebHistory(), | ||
routes, | ||
}); | ||
|
||
describe('PdapBreadcrumbs', () => { | ||
it('renders breadcrumbs correctly for a root route', async () => { | ||
router.push({ name: 'Product' }); | ||
await router.isReady(); | ||
|
||
const wrapper = mount(PdapBreadcrumbs, { | ||
global: { | ||
mocks: { | ||
$route: routes[0], | ||
}, | ||
plugins: [router], | ||
stubs: { | ||
RouterLink: RouterLinkStub, | ||
}, | ||
}, | ||
}); | ||
|
||
const breadcrumbItems = wrapper.findAll('li'); | ||
expect(breadcrumbItems).toHaveLength(1); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(0).text()).toBe('Product'); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(0).classes()).toContain('is-active'); | ||
}); | ||
|
||
it('renders breadcrumbs correctly for a single-nested route', async () => { | ||
router.push({ name: 'Product ID', params: { id: 1234 } }); | ||
await router.isReady(); | ||
await flushPromises(); | ||
|
||
const wrapper = mount(PdapBreadcrumbs, { | ||
global: { | ||
mocks: { | ||
$route: routes[0].children[0], | ||
}, | ||
plugins: [router], | ||
stubs: { | ||
RouterLink: RouterLinkStub, | ||
}, | ||
}, | ||
}); | ||
|
||
const breadcrumbItems = wrapper.findAll('li'); | ||
expect(breadcrumbItems).toHaveLength(2); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(0).text()).toBe('Product'); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(1).text()).toBe('Product ID'); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(1).classes()).toContain('is-active'); | ||
|
||
expect(wrapper.html()).toMatchSnapshot(); | ||
}); | ||
|
||
it('renders breadcrumbs correctly for a double-nested route', async () => { | ||
router.push({ name: 'Edit Product ID', params: { id: 1234 } }); | ||
await router.isReady(); | ||
await flushPromises(); | ||
|
||
const wrapper = mount(PdapBreadcrumbs, { | ||
global: { | ||
mocks: { | ||
$route: routes[0].children[0].children[0], | ||
}, | ||
plugins: [router], | ||
stubs: { | ||
RouterLink: RouterLinkStub, | ||
}, | ||
}, | ||
}); | ||
|
||
const breadcrumbItems = wrapper.findAll('li'); | ||
expect(breadcrumbItems).toHaveLength(3); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(0).text()).toBe('Product'); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(1).text()).toBe('Product ID'); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(2).text()).toBe('Edit Product ID'); | ||
// @ts-expect-error | ||
expect(breadcrumbItems.at(2).classes()).toContain('is-active'); | ||
|
||
expect(wrapper.html()).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import PdapBreadcrumbs from './PdapBreadcrumbs.vue'; | ||
|
||
export { PdapBreadcrumbs as Breadcrumbs }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// breadcrumbs.test.ts | ||
import { getBreadcrumbs, BreadcrumbItem } from './breadcrumbs'; | ||
import { RouteLocationNormalized } from 'vue-router'; | ||
import { describe, it, expect } from 'vitest'; | ||
|
||
describe('getBreadcrumbs', () => { | ||
it('should return an empty array when the route has no matched routes', () => { | ||
const route = { | ||
matched: [], | ||
// other properties omitted for brevity | ||
}; | ||
|
||
const breadcrumbs = getBreadcrumbs( | ||
route as unknown as RouteLocationNormalized | ||
); | ||
|
||
expect(breadcrumbs).toEqual([]); | ||
}); | ||
|
||
it('should return breadcrumbs with correct properties', () => { | ||
const route = { | ||
matched: [ | ||
{ | ||
name: 'Home', | ||
path: '/', | ||
meta: { breadcrumbText: 'Home' }, | ||
}, | ||
{ | ||
name: 'About', | ||
path: '/about', | ||
meta: {}, | ||
}, | ||
{ | ||
name: 'Contact', | ||
path: '/contact', | ||
meta: { breadcrumbText: 'Get in Touch' }, | ||
}, | ||
], | ||
// other properties omitted for brevity | ||
}; | ||
|
||
const expectedBreadcrumbs: BreadcrumbItem[] = [ | ||
{ | ||
text: 'Home', | ||
path: '/', | ||
active: false, | ||
}, | ||
{ | ||
text: 'About', | ||
path: '/about', | ||
active: false, | ||
}, | ||
{ | ||
text: 'Get in Touch', | ||
path: '/contact', | ||
active: true, | ||
}, | ||
]; | ||
|
||
const breadcrumbs = getBreadcrumbs( | ||
route as unknown as RouteLocationNormalized | ||
); | ||
|
||
expect(breadcrumbs).toEqual(expectedBreadcrumbs); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { RouteLocationNormalized } from 'vue-router'; | ||
|
||
export interface BreadcrumbItem { | ||
text: string; | ||
path: string; | ||
active: boolean; | ||
} | ||
|
||
export function getBreadcrumbs( | ||
route: RouteLocationNormalized | ||
): BreadcrumbItem[] { | ||
const breadcrumbs: BreadcrumbItem[] = []; | ||
|
||
for (const matched of route.matched) { | ||
const { name, path, meta } = matched; | ||
const breadcrumbText = meta.breadcrumbText ?? name; | ||
|
||
if (breadcrumbText) { | ||
breadcrumbs.push({ | ||
text: breadcrumbText as string, | ||
path, | ||
active: false, | ||
}); | ||
} | ||
} | ||
|
||
// Set "active" if breadcrumbs > 1 | ||
if (breadcrumbs.length > 0) { | ||
breadcrumbs[breadcrumbs.length - 1].active = true; | ||
} | ||
|
||
return breadcrumbs; | ||
} |