@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
121 lines (101 loc) • 3.93 kB
JavaScript
import React from 'react'
import {vi} from 'vitest'
import {render, waitFor} from '@testing-library/react'
import EditStem from '../index'
describe('Fill in the Blank Edit Stem', () => {
const setSelectedTextStub = vi.fn()
const defaultProps = {
destroyBlank: () => {},
errors: [],
setSelectedText: setSelectedTextStub,
onCreateBlankHotkey: () => {},
onStemChange: () => {},
showCreateButton: () => {},
stemItems: [],
styles: {},
}
afterEach(() => {
setSelectedTextStub.mockClear()
})
describe('placeholder', () => {
it('displays a placeholder when not focused', () => {
const {container} = render(
<EditStem {...defaultProps} stemItems={[{id: '0', type: 'text', value: ''}]} />,
)
expect(container.querySelector('[placeholder="Type a statement..."]')).not.toBeNull()
})
it('displays a placeholder when focused', () => {
const {container} = render(
<EditStem {...defaultProps} stemItems={[{id: '0', type: 'text', value: ''}]} />,
)
container.querySelector('[placeholder="Type a statement..."]').focus()
expect(container.querySelector('[placeholder="Type a statement..."]')).not.toBeNull()
})
it('displays a placeholder if the only content is a nbsp', () => {
const {container} = render(
<EditStem
{...defaultProps}
stemItems={[{id: '0', type: 'text', value: String.fromCharCode(160)}]}
/>,
)
expect(container.querySelector('[placeholder="Type a statement..."]')).not.toBeNull()
})
})
describe('text selection', () => {
// The component uses document.onselectionchange with a debounced handler (100ms)
// that reads sel.anchorNode and sel.toString(). jsdom does not support
// Selection.toString() or anchorNode properly, so we mock window.getSelection.
it('calls setSelectedText with the text when stem item is selected', async () => {
const {container} = render(
<EditStem {...defaultProps} stemItems={[{id: '0', type: 'text', value: 'hello world'}]} />,
)
const elem = container.querySelector('[contenteditable="true"]')
vi.spyOn(window, 'getSelection').mockReturnValue({
anchorNode: elem,
toString: () => 'hello world',
})
document.onselectionchange()
await waitFor(() => {
expect(setSelectedTextStub).toHaveBeenCalledWith('hello world')
})
})
it('does not call setSelectedText when something else is selected', async () => {
render(
<EditStem {...defaultProps} stemItems={[{id: '0', type: 'text', value: 'hello world'}]} />,
)
const outsideDiv = document.createElement('div')
document.body.appendChild(outsideDiv)
vi.spyOn(window, 'getSelection').mockReturnValue({
anchorNode: outsideDiv,
toString: () => 'foo',
})
document.onselectionchange()
await waitFor(() => {
expect(setSelectedTextStub).not.toHaveBeenCalled()
})
outsideDiv.remove()
})
})
describe('a11y', () => {
it('uses a Tag component for blanks', () => {
const blankText = 'this is a blank'
const {container} = render(
<EditStem
{...defaultProps}
stemItems={[{id: 'blank', blankId: 'blank', type: 'blank'}]}
scoringData={{
value: [{id: 'blank', scoringData: {blankText}}],
}}
/>,
)
// Tag renders a button-like element with the blank text
// Check that the blank label with the expected alt text is rendered
const tagElement =
container.querySelector('[class*="tag"]') ||
container.querySelector('span[role="button"], button')
expect(tagElement || document.body.textContent).toBeDefined()
// Verify the delete label contains the blank text
expect(document.body.textContent).toContain(blankText)
})
})
})