Skip to content

Commit dd6faf4

Browse files
committed
test(ui-select,ui-options): migrate old Options tests
1 parent dd2638e commit dd6faf4

File tree

16 files changed

+521
-634
lines changed

16 files changed

+521
-634
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
import React from 'react'
25+
import '../support/component'
26+
import 'cypress-real-events'
27+
import { expect } from 'chai'
28+
import { Options } from '@instructure/ui'
29+
import { IconCheckSolid } from '@instructure/ui-icons'
30+
31+
describe('<OptionsItem/>', () => {
32+
it('should allow label to recieve focus', async () => {
33+
const onFocus = cy.spy()
34+
cy.mount(
35+
<Options.Item tabIndex={-1} onFocus={onFocus}>
36+
Hello World
37+
</Options.Item>
38+
)
39+
cy.get('span[role="listitem"]').as('item')
40+
41+
cy.get('@item').focus()
42+
cy.wrap(onFocus).should('have.been.called')
43+
cy.contains('Hello World').should('be.focused')
44+
})
45+
46+
it('should render colored icon before label', async () => {
47+
cy.mount(
48+
<Options.Item
49+
renderBeforeLabel={(props) => {
50+
return (
51+
<IconCheckSolid
52+
{...(props.variant === 'default' && { color: 'warning' })}
53+
/>
54+
)
55+
}}
56+
>
57+
Hello World
58+
</Options.Item>
59+
)
60+
61+
cy.get('[class$="-optionItem__content--before"]')
62+
.find('svg[name="IconCheck"]')
63+
.then(($icon) => {
64+
const iconStyle = getComputedStyle($icon[0])
65+
expect(iconStyle.color).to.equal('rgb(207, 74, 0)')
66+
})
67+
})
68+
69+
it('should render colored icon after highlighted label', async () => {
70+
cy.mount(
71+
<Options.Item
72+
variant="highlighted"
73+
renderAfterLabel={(props) => {
74+
return (
75+
<IconCheckSolid
76+
{...(props.variant === 'highlighted' && { color: 'success' })}
77+
/>
78+
)
79+
}}
80+
>
81+
Hello World
82+
</Options.Item>
83+
)
84+
cy.get('[class$="-optionItem__content--after"]')
85+
.find('svg[name="IconCheck"]')
86+
.then(($icon) => {
87+
const iconStyle = getComputedStyle($icon[0])
88+
expect(iconStyle.color).to.equal('rgb(3, 137, 61)')
89+
})
90+
})
91+
})

package-lock.json

Lines changed: 6 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ui-options/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
},
2424
"license": "MIT",
2525
"devDependencies": {
26+
"@instructure/ui-axe-check": "10.12.0",
2627
"@instructure/ui-babel-preset": "10.12.0",
2728
"@instructure/ui-color-utils": "10.12.0",
28-
"@instructure/ui-test-locator": "10.12.0",
2929
"@instructure/ui-test-utils": "10.12.0",
30-
"@instructure/ui-themes": "10.12.0"
30+
"@instructure/ui-themes": "10.12.0",
31+
"@testing-library/jest-dom": "^6.6.3",
32+
"@testing-library/react": "^16.0.1",
33+
"@testing-library/user-event": "^14.5.2",
34+
"vitest": "^2.1.8"
3135
},
3236
"dependencies": {
3337
"@babel/runtime": "^7.26.0",

packages/ui-options/src/Options/Item/OptionsItemLocator.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
import React from 'react'
26+
import { render, screen, waitFor } from '@testing-library/react'
27+
import { vi, expect } from 'vitest'
28+
import userEvent from '@testing-library/user-event'
29+
30+
import '@testing-library/jest-dom'
31+
import { IconCheckSolid } from '@instructure/ui-icons'
32+
import { Options } from '../../index'
33+
import { Item } from '../index'
34+
35+
describe('<Item />', () => {
36+
it('should render', async () => {
37+
const { container } = render(<Item />)
38+
39+
const optionItem = container.querySelector('[class$="-optionItem"]')
40+
expect(optionItem).toBeInTheDocument()
41+
42+
const optionItemContainer = optionItem!.querySelector(
43+
'[class$="-optionItem__container"]'
44+
)
45+
expect(optionItemContainer).toBeInTheDocument()
46+
})
47+
48+
it('should not render as a list item by default', async () => {
49+
const { container } = render(<Item>Hello World</Item>)
50+
51+
const item = container.querySelector('[class$="-optionItem"]')
52+
53+
expect(item).toBeInTheDocument()
54+
expect(item!.tagName).not.toBe('LI')
55+
})
56+
57+
it('should render designated tag if `as` prop is specified', async () => {
58+
const { container } = render(<Item as="li">Hello World</Item>)
59+
60+
const item = container.querySelector('[class$="-optionItem"]')
61+
62+
expect(item).toBeInTheDocument()
63+
expect(item!.tagName).toBe('LI')
64+
})
65+
66+
it('should render children properly', async () => {
67+
const { container } = render(
68+
<Item>
69+
<span id="customContent">Hello World</span>
70+
</Item>
71+
)
72+
73+
const item = container.querySelector('[class$="-optionItem"]')
74+
const customContent = item!.querySelector('#customContent')
75+
76+
expect(customContent).toHaveTextContent('Hello World')
77+
})
78+
79+
it('should render role attributes appropriately when given a role', async () => {
80+
const { container } = render(<Item role="option">Hello World</Item>)
81+
82+
const item = container.querySelector('[class$="-optionItem"]')
83+
const child = screen.getByRole('option')
84+
85+
expect(item).toHaveAttribute('role', 'none')
86+
expect(child).toBeInTheDocument()
87+
})
88+
89+
it('should render description properly', async () => {
90+
const { container } = render(
91+
<Item description="Some text as description">
92+
<span id="customContent">Hello World</span>
93+
</Item>
94+
)
95+
const item = container.querySelector('[class$="-optionItem"]')
96+
97+
const customContent = item!.querySelector('#customContent')
98+
const description = item!.querySelector('[class$="__description"]')
99+
100+
expect(customContent).toHaveTextContent('Hello World')
101+
expect(description).toHaveTextContent('Some text as description')
102+
})
103+
104+
it('should render role attributes for description', async () => {
105+
render(
106+
<Item description="Some text as description" descriptionRole="comment">
107+
Hello World
108+
</Item>
109+
)
110+
const description = screen.getByRole('comment')
111+
112+
expect(description).toBeInTheDocument()
113+
expect(description).toHaveTextContent('Some text as description')
114+
})
115+
116+
it('should pass props through to label', async () => {
117+
const { container } = render(
118+
<Item role="option" tabIndex={-1} data-custom-attr="true">
119+
Hello World
120+
</Item>
121+
)
122+
const optionItem = container.querySelector('[class$="-optionItem"]')
123+
const optionItemContainer = optionItem!.querySelector(
124+
'[class$="-optionItem__container"]'
125+
)
126+
127+
expect(optionItem).toHaveRole('none')
128+
expect(optionItemContainer).toHaveRole('option')
129+
expect(optionItemContainer).toHaveAttribute('tabindex', '-1')
130+
expect(optionItemContainer).toHaveAttribute('data-custom-attr', 'true')
131+
})
132+
133+
it('should pass event handlers through to label', async () => {
134+
const onClick = vi.fn()
135+
const { container } = render(<Item onClick={onClick}>Hello World</Item>)
136+
137+
const optionItem = container.querySelector('[class$="-optionItem"]')
138+
const optionItemContainer = container.querySelector(
139+
'[class$="-optionItem__container"]'
140+
)
141+
142+
userEvent.click(optionItem!)
143+
await waitFor(() => {
144+
expect(onClick).not.toHaveBeenCalled()
145+
})
146+
147+
userEvent.click(optionItemContainer!)
148+
await waitFor(() => {
149+
expect(onClick).toHaveBeenCalledTimes(1)
150+
})
151+
})
152+
153+
it('should render content before label', async () => {
154+
const { container } = render(
155+
<Item renderBeforeLabel={<IconCheckSolid />}>Hello World</Item>
156+
)
157+
158+
const content = container.querySelector(
159+
'[class$=-optionItem__content--before]'
160+
)
161+
expect(content).toBeInTheDocument()
162+
163+
const icon = content!.querySelector('svg[name="IconCheck"]')
164+
expect(icon).toBeInTheDocument()
165+
})
166+
167+
it('should render content after label', async () => {
168+
const { container } = render(
169+
<Item renderAfterLabel={<IconCheckSolid />}>Hello World</Item>
170+
)
171+
172+
const content = container.querySelector(
173+
'[class$=-optionItem__content--after]'
174+
)
175+
expect(content).toBeInTheDocument()
176+
177+
const icon = content!.querySelector('svg[name="IconCheck"]')
178+
expect(icon).toBeInTheDocument()
179+
})
180+
181+
it('should render nested lists', async () => {
182+
const { container } = render(
183+
<Item>
184+
<Options as="ul" renderLabel={'Nested list'}>
185+
<Item>Sub item</Item>
186+
</Options>
187+
</Item>
188+
)
189+
190+
const item = container.querySelector('span[class$="-optionItem"]')
191+
expect(item).toBeInTheDocument()
192+
193+
const options = item!.querySelector('div[class$="-options"]')
194+
expect(options).toBeInTheDocument()
195+
196+
const nestedList = options!.querySelector('ul')
197+
const nestedItem = options!.querySelector('li[class$="-optionItem"]')
198+
199+
expect(nestedList).toBeInTheDocument()
200+
expect(nestedItem).toBeInTheDocument()
201+
expect(nestedItem).toHaveTextContent('Sub item')
202+
})
203+
204+
it('should render as link with href prop', async () => {
205+
const { container } = render(<Item href="/helloWorld">Hello World</Item>)
206+
207+
const link = container.querySelector('[class$="-optionItem__container"]')
208+
209+
expect(link).toBeInTheDocument()
210+
expect(link).toHaveAttribute('href', '/helloWorld')
211+
expect(link?.tagName).toBe('A')
212+
})
213+
})

0 commit comments

Comments
 (0)