Skip to content
This repository was archived by the owner on Sep 20, 2024. It is now read-only.

Commit 4e535ef

Browse files
committed
test(breadcrumb): write breadcrumb tests
1 parent 8895318 commit 4e535ef

File tree

7 files changed

+357
-16
lines changed

7 files changed

+357
-16
lines changed

packages/c-breadcrumb/README.md

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,110 @@ Breadcrumbs help users visualize their current location in relation to the rest
88
yarn add @chakra-ui/c-breadcrumb
99
# or
1010
npm i @chakra-ui/c-breadcrumb
11-
```
11+
```
12+
13+
14+
15+
## Import components
16+
17+
Chakra UI Vue exports 3 breadcrumb related components:
18+
19+
- `CBreadcrumb`: The parent container for breadcrumbs.
20+
- `CBreadcrumbItem`: Individual breadcrumb element containing a link and a
21+
divider.
22+
- `CBreadcrumbLink`: The breadcrumb link.
23+
24+
```js
25+
import { CBreadcrumb, CBreadcrumbItem, CBreadcrumbLink } from "@chakra-ui/vue-next"
26+
```
27+
28+
## Usage
29+
30+
Add `isCurrentPage` prop to the `CBreadcrumbItem` that matches the current path.
31+
When this prop is present, the `CBreadcrumbItem` doesn't have a separator, and
32+
the `CBreadcrumbLink` has `aria-current` set to `page`.
33+
34+
```html
35+
<c-breadcrumb>
36+
<c-breadcrumb-item>
37+
<c-breadcrumb-link href="#">Home</c-breadcrumb-link>
38+
</c-breadcrumb-item>
39+
40+
<c-breadcrumb-item>
41+
<c-breadcrumb-link as="router-link" to="/docs">Docs</c-breadcrumb-link>
42+
</c-breadcrumb-item>
43+
44+
<c-breadcrumb-item isCurrentPage>
45+
<c-breadcrumb-link>Help</c-breadcrumb-link>
46+
</c-breadcrumb-item>
47+
</c-breadcrumb>
48+
```
49+
50+
### Separators
51+
52+
Change the separator used in the breadcrumb by passing a string, like `-` element.
53+
54+
```html
55+
<c-breadcrumb separator="-">
56+
<c-breadcrumb-item>
57+
<c-breadcrumb-link href="#">Home</c-breadcrumb-link>
58+
</c-breadcrumb-item>
59+
60+
<c-breadcrumb-item>
61+
<c-breadcrumb-link as="router-link" to="/docs">Docs</c-breadcrumb-link>
62+
</c-breadcrumb-item>
63+
64+
<c-breadcrumb-item isCurrentPage>
65+
<c-breadcrumb-link>Help</c-breadcrumb-link>
66+
</c-breadcrumb-item>
67+
</c-breadcrumb>
68+
```
69+
### Using the separator slot
70+
71+
You can override the rendered spearator by using the `v-slot:separator` which will render any component you want as the separator for the `CBreadcrumb` component
72+
73+
```html
74+
<c-breadcrumb>
75+
<template v-slot:separator>
76+
<c-icon name="chevron-right" />
77+
</template>
78+
<c-breadcrumb-item>
79+
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
80+
</c-breadcrumb-item>
81+
<c-breadcrumb-item>
82+
<c-breadcrumb-link href="#">Docs</c-breadcrumb-link>
83+
</c-breadcrumb-item>
84+
<c-breadcrumb-item is-current-page>
85+
<c-breadcrumb-link href="#">About</c-breadcrumb-link>
86+
</c-breadcrumb-item>
87+
</c-breadcrumb>
88+
```
89+
### Using a functional icon component as the separator
90+
91+
You can also pass a functional component into the `:separator` prop
92+
93+
```html
94+
95+
<template>
96+
<c-breadcrumb :separator="SunIcon">
97+
<c-breadcrumb-item>
98+
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
99+
</c-breadcrumb-item>
100+
<c-breadcrumb-item>
101+
<c-breadcrumb-link href="#">Docs</c-breadcrumb-link>
102+
</c-breadcrumb-item>
103+
<c-breadcrumb-item is-current-page>
104+
<c-breadcrumb-link href="#">About</c-breadcrumb-link>
105+
</c-breadcrumb-item>
106+
</c-breadcrumb>
107+
</template>
108+
109+
<script setup>
110+
const SunIcon = () => {
111+
return h(CIcon, {
112+
name: 'sun',
113+
})
114+
}
115+
</script>
116+
117+
```

packages/c-breadcrumb/examples/simple-breadcrumb.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<template>
22
<c-breadcrumb>
3-
<template v-slot:separator>
4-
<c-icon name="chevron-right" />
5-
</template>
63
<c-breadcrumb-item>
74
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
85
</c-breadcrumb-item>
@@ -16,7 +13,14 @@
1613
</template>
1714

1815
<script lang="ts" setup>
16+
import { defineComponent, h } from 'vue'
1917
import { useRoute } from 'vue-router'
18+
import { CIcon } from '@chakra-ui/vue-next'
2019
20+
const Sun = () => {
21+
return h(CIcon, {
22+
name: 'sun',
23+
})
24+
}
2125
const route = useRoute()
2226
</script>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<template>
2+
<c-stack>
3+
<c-heading size="sm" color="tomato"> With default separator </c-heading>
4+
<c-breadcrumb>
5+
<c-breadcrumb-item>
6+
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
7+
</c-breadcrumb-item>
8+
<c-breadcrumb-item>
9+
<c-breadcrumb-link href="#">Docs</c-breadcrumb-link>
10+
</c-breadcrumb-item>
11+
<c-breadcrumb-item is-current-page>
12+
<c-breadcrumb-link href="#">About</c-breadcrumb-link>
13+
</c-breadcrumb-item>
14+
</c-breadcrumb>
15+
<c-heading size="sm" color="tomato"> With separator as string </c-heading>
16+
<c-breadcrumb separator=">">
17+
<c-breadcrumb-item>
18+
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
19+
</c-breadcrumb-item>
20+
<c-breadcrumb-item>
21+
<c-breadcrumb-link href="#">Docs</c-breadcrumb-link>
22+
</c-breadcrumb-item>
23+
<c-breadcrumb-item is-current-page>
24+
<c-breadcrumb-link href="#">About</c-breadcrumb-link>
25+
</c-breadcrumb-item>
26+
</c-breadcrumb>
27+
<c-heading size="sm" color="tomato">
28+
With separator props as functional component
29+
</c-heading>
30+
<c-breadcrumb :separator="Sun">
31+
<c-breadcrumb-item>
32+
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
33+
</c-breadcrumb-item>
34+
<c-breadcrumb-item>
35+
<c-breadcrumb-link href="#">Docs</c-breadcrumb-link>
36+
</c-breadcrumb-item>
37+
<c-breadcrumb-item is-current-page>
38+
<c-breadcrumb-link href="#">About</c-breadcrumb-link>
39+
</c-breadcrumb-item>
40+
</c-breadcrumb>
41+
<c-heading size="sm" color="tomato"> With separator slot </c-heading>
42+
<c-breadcrumb>
43+
<template v-slot:separator>
44+
<c-icon name="chevron-right" />
45+
</template>
46+
<c-breadcrumb-item>
47+
<c-breadcrumb-link as="router-link" to="/">Home</c-breadcrumb-link>
48+
</c-breadcrumb-item>
49+
<c-breadcrumb-item>
50+
<c-breadcrumb-link href="#">Docs</c-breadcrumb-link>
51+
</c-breadcrumb-item>
52+
<c-breadcrumb-item is-current-page>
53+
<c-breadcrumb-link href="#">About</c-breadcrumb-link>
54+
</c-breadcrumb-item>
55+
</c-breadcrumb>
56+
</c-stack>
57+
</template>
58+
59+
<script lang="ts" setup>
60+
import { defineComponent, h } from 'vue'
61+
import { useRoute } from 'vue-router'
62+
import { CIcon } from '@chakra-ui/vue-next'
63+
64+
const Sun = () => {
65+
return h(CIcon, {
66+
name: 'sun',
67+
})
68+
}
69+
const route = useRoute()
70+
</script>

packages/c-breadcrumb/src/c-breadcrumb.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @see WAI-ARIA https://www.w3.org/TR/wai-aria-practices-1.2
99
*/
1010

11-
import { defineComponent, PropType, computed, cloneVNode, VNode, h } from 'vue'
11+
import { defineComponent, PropType, computed, cloneVNode, h, VNode } from 'vue'
1212
import {
1313
chakra,
1414
HTMLChakraProps,
@@ -21,7 +21,7 @@ import {
2121
ChakraProps
2222
} from '@chakra-ui/vue-system'
2323
import { filterUndefined } from '@chakra-ui/utils'
24-
import { getValidChildren, SNA, SNAO } from '@chakra-ui/vue-utils'
24+
import { getValidChildren, isObjectComponent, SNA, SNAO } from '@chakra-ui/vue-utils'
2525
import { DOMElements } from '@chakra-ui/vue-system'
2626

2727
/**
@@ -67,7 +67,24 @@ export const CBreadcrumb = defineComponent(
6767
const styles = useMultiStyleConfig('Breadcrumb', themingProps.value)
6868
StylesProvider(styles)
6969

70-
const separator = computed(() => slots?.separator?.() || props.separator)
70+
const separator = computed(() => {
71+
if (slots.separator) {
72+
return slots?.separator?.()
73+
} else {
74+
return typeof props.separator === 'string'
75+
? props.separator
76+
: isObjectComponent(props.separator!)
77+
// TODO:
78+
// Add support for
79+
// object components. ATM,
80+
// This computed property will only
81+
// work for functional components provided as
82+
// separators
83+
? h(() => props.separator!)
84+
: h(props.separator!)
85+
}
86+
}
87+
)
7188

7289
return () => {
7390
const validChildren = getValidChildren(slots)

packages/c-breadcrumb/tests/c-breadcrumb.cy.tsx

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as Examples from '../examples'
22
import { h } from 'vue'
33
// import { cy } from 'local-cypress'
44
import { CBreadcrumb, CBreadcrumbItem, CBreadcrumbLink } from '../src'
5+
import { CIcon } from '../../c-icon/src'
56

67
describe('Breadcrumb Examples', () => {
78
Object.entries(Examples).map(([name, example]: any) => {
@@ -14,12 +15,12 @@ describe('Breadcrumb Examples', () => {
1415
const render = (props: any = {}) => {
1516
return cy.mount(
1617
h(() => (
17-
<CBreadcrumb data-testid="breadcrumb" separator="-" {...props}>
18+
<CBreadcrumb data-testid="breadcrumb" {...props}>
1819
<CBreadcrumbItem>
1920
<CBreadcrumbLink href="/">Home</CBreadcrumbLink>
2021
</CBreadcrumbItem>
2122

22-
<CBreadcrumbItem>
23+
<CBreadcrumbItem data-testid="with-separator">
2324
<CBreadcrumbLink href="/about">About</CBreadcrumbLink>
2425
</CBreadcrumbItem>
2526

@@ -31,10 +32,20 @@ const render = (props: any = {}) => {
3132
)
3233
}
3334

34-
it('contains the correct role', () => {
35-
cy.mount(Examples.SimpleBreadcrumb.default).get('[aria-label=breadcrumb]').should('exist')
35+
36+
describe('ARIA roles and attributes', () => {
37+
it('contains the correct role', () => {
38+
cy.mount(Examples.SimpleBreadcrumb.default).get('[aria-label=breadcrumb]').should('exist')
39+
})
40+
41+
it('current page should have `aria-current="page"` attribute', () => {
42+
render()
43+
.get('[data-testid=current-page] > span')
44+
.should('have.attr', 'aria-current', 'page')
45+
})
3646
})
3747

48+
3849
it('renders its children', () => {
3950
render()
4051
.get('[data-testid=breadcrumb]')
@@ -43,8 +54,45 @@ it('renders its children', () => {
4354
.should('contain', 'Contact')
4455
})
4556

46-
it('current page should have `aria-current="page"` attribute', () => {
47-
render()
48-
.get('[data-testid=current-page] > span')
49-
.should('have.attr', 'aria-current', 'page')
50-
})
57+
58+
describe('Separator tests', () => {
59+
it('should display the accepted :separator prop', () => {
60+
render({ separator: '-'})
61+
.get('[data-testid=with-separator] > span')
62+
.should('contain', '-')
63+
render({ separator: '>'})
64+
.get('[data-testid=with-separator] > span')
65+
.should('contain', '>')
66+
})
67+
68+
it('should not display separator for last child', () => {
69+
render({ separator: '-'})
70+
.get('[data-testid=breadcrumb] > ol > li').last()
71+
.should('not.contain', '-')
72+
})
73+
74+
it('should render separator as Functional component if provided', () => {
75+
const Sun = () => <CIcon data-testid="custom-separator" name="sun" v-slot:separator/>
76+
cy.mount(
77+
h(() => (
78+
<CBreadcrumb separator={Sun} data-testid="breadcrumb">
79+
<CBreadcrumbItem>
80+
<CBreadcrumbLink href="/">Home</CBreadcrumbLink>
81+
</CBreadcrumbItem>
82+
83+
<CBreadcrumbItem data-testid="with-separator">
84+
<CBreadcrumbLink href="/about">About</CBreadcrumbLink>
85+
</CBreadcrumbItem>
86+
87+
<CBreadcrumbItem data-testid="current-page" isCurrentPage>
88+
<CBreadcrumbLink href="/contact">Contact</CBreadcrumbLink>
89+
</CBreadcrumbItem>
90+
</CBreadcrumb>
91+
))
92+
)
93+
.get('[data-testid=breadcrumb] > ol > li')
94+
.find('[data-testid=custom-separator]')
95+
.should('exist')
96+
})
97+
98+
})

packages/utils/src/vue-utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isFunction, isObject } from '@chakra-ui/utils'
12
import { inject, InjectionKey, provide, isVNode, Slots, VNode } from 'vue'
23

34
export interface CreateContextOptions {
@@ -63,3 +64,27 @@ export function getValidChildren(slots: Slots | null): VNode[] {
6364
return isVNode(child)
6465
})
6566
}
67+
68+
export interface CouldBeObjectComponent {
69+
setup?: FunctionConstructor
70+
render?: FunctionConstructor
71+
}
72+
73+
/** Checkes whether a provided object is a component */
74+
export function isObjectComponent<T extends CouldBeObjectComponent>(
75+
subject: T
76+
) {
77+
const validComponentTypes = ['function', 'object']
78+
if (!validComponentTypes.includes(typeof subject)) return false
79+
80+
// Is sub
81+
if (isObject(subject)) {
82+
// Is object component with render function
83+
if (typeof subject?.render === 'function' && isVNode(subject.render()))
84+
return true
85+
// Is object component with setup function
86+
else if (typeof subject?.setup === 'function') return true
87+
}
88+
89+
return false
90+
}

0 commit comments

Comments
 (0)