@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
274 lines (213 loc) • 8.09 kB
JavaScript
import {vi, beforeAll} from 'vitest'
import React from 'react'
import {render, fireEvent} from '@testing-library/react'
import Polygon from '../index'
// vitest-canvas-mock doesn't fully patch getContext in vitest 4.
// Ensure canvas elements return a mocked 2d context.
beforeAll(() => {
const originalGetContext = HTMLCanvasElement.prototype.getContext
HTMLCanvasElement.prototype.getContext = function (type, ...args) {
const ctx = originalGetContext.call(this, type, ...args)
if (ctx) return ctx
// Fallback: return a mock context if the real one is null
return {
clearRect: vi.fn(),
beginPath: vi.fn(),
moveTo: vi.fn(),
lineTo: vi.fn(),
stroke: vi.fn(),
arc: vi.fn(),
fill: vi.fn(),
closePath: vi.fn(),
setLineDash: vi.fn(),
save: vi.fn(),
restore: vi.fn(),
fillRect: vi.fn(),
strokeRect: vi.fn(),
canvas: this,
globalAlpha: 1,
strokeStyle: '',
fillStyle: '',
lineWidth: 1,
}
}
})
const handleSetCoordinatesStub = vi.fn()
const props = {
coordinates: [
{x: 10, y: 10},
{x: 10, y: 20},
{x: 20, y: 20},
],
height: 100,
width: 100,
handleSetCoordinates: handleSetCoordinatesStub,
}
describe('Polygon', () => {
let polygon
function renderPolygon(customProps = {}) {
render(
<Polygon
ref={node => {
polygon = node
}}
{...props}
{...customProps}
/>,
)
}
afterEach(() => {
handleSetCoordinatesStub.mockReset()
vi.restoreAllMocks()
})
describe('#__drawLine', () => {
it('draws a line on canvas given begin and end coordinates', () => {
renderPolygon()
polygon.clearCanvas()
const begin = props.coordinates[0]
const end = props.coordinates[1]
const ctx = polygon.canvas.canvasElement.getContext('2d')
const moveToSpy = vi.spyOn(ctx, 'moveTo')
const lineToSpy = vi.spyOn(ctx, 'lineTo')
const strokeSpy = vi.spyOn(ctx, 'stroke')
polygon.canvas.setStrokeStyle()
polygon.__drawLine(begin, end)
expect(moveToSpy).toHaveBeenCalledWith(begin.x, begin.y)
expect(lineToSpy).toHaveBeenCalledWith(end.x, end.y)
expect(strokeSpy).toHaveBeenCalled()
})
})
describe('#clearCanvas', () => {
it('clears the canvas', () => {
renderPolygon()
polygon.drawShape(props.coordinates)
const ctx = polygon.canvas.canvasElement.getContext('2d')
const clearRectSpy = vi.spyOn(ctx, 'clearRect')
polygon.clearCanvas()
expect(clearRectSpy).toHaveBeenCalledWith(0, 0, props.width, props.height)
})
})
describe('#drawShape', () => {
it('draws a polygon on canvas based on the given coordinates', () => {
renderPolygon()
polygon.clearCanvas()
const ctx = polygon.canvas.canvasElement.getContext('2d')
const moveToSpy = vi.spyOn(ctx, 'moveTo')
const lineToSpy = vi.spyOn(ctx, 'lineTo')
const fillSpy = vi.spyOn(ctx, 'fill')
polygon.drawShape(props.coordinates)
const coordA = props.coordinates[0]
const coordB = props.coordinates[1]
const coordC = props.coordinates[2]
// first line starts from A and goes to B
expect(moveToSpy).toHaveBeenCalledWith(coordA.x, coordA.y)
expect(lineToSpy).toHaveBeenCalledWith(coordB.x, coordB.y)
// second line goes to C
expect(lineToSpy).toHaveBeenCalledWith(coordC.x, coordC.y)
// shape is filled
expect(fillSpy).toHaveBeenCalled()
})
describe('when the given list of coordinates is empty', () => {
it('does nothing', () => {
renderPolygon()
const fill = vi.fn()
vi.spyOn(polygon.canvas.canvasElement, 'getContext').mockReturnValue({fill})
polygon.drawShape([])
expect(fill).not.toHaveBeenCalled()
})
})
})
describe('events', () => {
describe('onMouseMove', () => {
it('draws a line from the last coordinate to the current coordinate', () => {
renderPolygon({coordinates: [{x: 2, y: 2}]})
polygon.canvas.clearCanvas()
polygon.canvas.state.isDrawing = true
// Mock getCurrentCoordinates since RTL fireEvent doesn't set nativeEvent.offsetX
vi.spyOn(polygon.canvas, 'getCurrentCoordinates').mockReturnValue({x: 10, y: 10})
const clearCanvasSpy = vi.spyOn(polygon, 'clearCanvas')
const drawShapeSpy = vi.spyOn(polygon, 'drawShape')
fireEvent.mouseMove(polygon.canvas.canvasElement)
expect(clearCanvasSpy).toHaveBeenCalled()
expect(drawShapeSpy).toHaveBeenCalledWith([
{x: 2, y: 2},
{x: 10, y: 10},
])
})
})
describe('onMouseOut', () => {
it('updates the coordinates list including the mouseOut event coordinate and finishes the drawing', () => {
renderPolygon({
coordinates: [
{x: 5, y: 5},
{x: 10, y: 10},
{x: 15, y: 15},
],
})
polygon.canvas.clearCanvas()
polygon.canvas.state.isDrawing = true
const setStateSpy = vi.spyOn(polygon.canvas, 'setState')
fireEvent.mouseOut(polygon.canvas.canvasElement)
const newCoordinates = handleSetCoordinatesStub.mock.calls[0][0]
const newState = setStateSpy.mock.calls[0][0]
expect(newCoordinates.length).toBe(4)
// and the first coordinate is added at the end of the array to close the polygon
expect(newCoordinates[3].x).toBe(5)
expect(newCoordinates[3].y).toBe(5)
expect(newState.isDrawing).toBe(false)
})
})
describe('onDoubleClick', () => {
it('update the coordinates list and finishes the drawing', () => {
renderPolygon()
polygon.canvas.clearCanvas()
polygon.canvas.state.isDrawing = true
const setStateSpy = vi.spyOn(polygon.canvas, 'setState')
fireEvent.dblClick(polygon.canvas.canvasElement)
const newCoordinates = handleSetCoordinatesStub.mock.calls[0][0]
const newState = setStateSpy.mock.calls[0][0]
expect(newCoordinates.length).toBe(4)
// the last item has to be first one
expect(newCoordinates[3].x).toBe(10)
expect(newCoordinates[3].y).toBe(10)
expect(newState.isDrawing).toBe(false)
})
})
describe('onClick', () => {
it('starts drawing', () => {
renderPolygon({coordinates: []})
polygon.canvas.clearCanvas()
vi.spyOn(polygon.canvas, 'getCurrentCoordinates').mockReturnValue({x: 10, y: 10})
const setStateSpy = vi.spyOn(polygon.canvas, 'setState')
fireEvent.click(polygon.canvas.canvasElement)
const newCoordinates = handleSetCoordinatesStub.mock.calls[0][0]
const newState = setStateSpy.mock.calls[0][0]
expect(newCoordinates.length).toBe(1)
// the new coordinate
expect(newCoordinates[0].x).toBe(10)
expect(newCoordinates[0].y).toBe(10)
expect(newState.isDrawing).toBe(true)
})
it('adds a new coordinate when drawing', () => {
renderPolygon()
polygon.canvas.clearCanvas()
polygon.canvas.state.isDrawing = true
vi.spyOn(polygon.canvas, 'getCurrentCoordinates').mockReturnValue({x: 13, y: 14})
fireEvent.click(polygon.canvas.canvasElement)
const newCoordinates = handleSetCoordinatesStub.mock.calls[0][0]
expect(newCoordinates.length).toBe(4)
// the new coordinate
expect(newCoordinates[3].x).toBe(13)
expect(newCoordinates[3].y).toBe(14)
})
it('does nothing when the click happens at the same place of the previous click', () => {
renderPolygon({coordinates: [{x: 10, y: 10}]})
polygon.canvas.clearCanvas()
polygon.canvas.state.isDrawing = true
vi.spyOn(polygon.canvas, 'getCurrentCoordinates').mockReturnValue({x: 10, y: 10})
fireEvent.click(polygon.canvas.canvasElement)
expect(handleSetCoordinatesStub).not.toHaveBeenCalled()
})
})
})
})