/** * @vitest-environment jsdom */ import { describe, expect, it } from 'vitest' import { createSimpleOverlay, createSimplePositionCalculator } from './positioning-simple.js' import type { Identifier } from './types.js' // Helper to create test identifier const createTestIdentifier = ( name: string, line: number, column: number, kind: Identifier['kind'] = 'Field', ): Identifier => ({ name, kind, position: { start: column - 1, end: column - 1 + name.length, line, column, }, schemaPath: [name], context: { selectionPath: [] }, }) describe('Simple Positioning Engine', () => { describe('SimplePositionCalculator', () => { const calculator = createSimplePositionCalculator() it('should wrap identifiers in spans', () => { const container = document.createElement('div') container.innerHTML = `
query GetUser {
user {
name
}
}
`
const identifiers = [
createTestIdentifier('query', 1, 1),
createTestIdentifier('user', 2, 3, 'Field'),
createTestIdentifier('name', 3, 5, 'Field'),
]
const codeElement = container.querySelector('code')!
calculator.prepareCodeBlock(codeElement, identifiers)
// Check that identifiers were wrapped
const wrappedElements = container.querySelectorAll('[data-graphql-id]')
expect(wrappedElements.length).toBe(3)
// Check first wrapped element
const firstWrapped = wrappedElements[0] as HTMLElement
expect(firstWrapped.textContent).toBe('query')
expect(firstWrapped.getAttribute('data-graphql-name')).toBe('query')
expect(firstWrapped.getAttribute('data-graphql-kind')).toBe('Field')
})
it('should handle multiple identifiers in same line', () => {
const container = document.createElement('div')
container.innerHTML = `
query GetUserById($id: ID!) {
`
const identifiers = [
createTestIdentifier('query', 1, 1),
createTestIdentifier('GetUserById', 1, 7),
createTestIdentifier('$id', 1, 19, 'Variable'),
createTestIdentifier('ID', 1, 24, 'Type'),
]
const codeElement = container.querySelector('code')!
calculator.prepareCodeBlock(codeElement, identifiers)
const wrappedElements = container.querySelectorAll('[data-graphql-id]')
expect(wrappedElements.length).toBe(4)
})
it('should get positions of wrapped identifiers', () => {
const container = document.createElement('div')
container.innerHTML = `
user {
`
// Mock getBoundingClientRect
container.getBoundingClientRect = () => ({
top: 0,
left: 0,
right: 100,
bottom: 100,
width: 100,
height: 100,
x: 0,
y: 0,
toJSON: () => ({}),
})
const userSpan = container.querySelector('[data-graphql-id]')!
userSpan.getBoundingClientRect = () => ({
top: 10,
left: 20,
right: 60,
bottom: 30,
width: 40,
height: 20,
x: 20,
y: 10,
toJSON: () => ({}),
})
const positions = calculator.getIdentifierPositions(container)
expect(positions.size).toBe(1)
const result = positions.get('0-user-Field')
expect(result).toBeDefined()
expect(result!.position).toEqual({
top: 10,
left: 20,
width: 40,
height: 20,
})
expect(result!.identifier.name).toBe('user')
expect(result!.identifier.kind).toBe('Field')
})
it('should skip already wrapped identifiers', () => {
const container = document.createElement('div')
container.innerHTML = `
user {
`
const identifiers = [
createTestIdentifier('user', 1, 1),
]
const codeElement = container.querySelector('code')!
calculator.prepareCodeBlock(codeElement, identifiers)
// Should still only have one wrapped element
const wrappedElements = container.querySelectorAll('[data-graphql-id]')
expect(wrappedElements.length).toBe(1)
expect(wrappedElements[0]!.getAttribute('data-graphql-id')).toBe('existing')
})
it('should handle empty lines gracefully', () => {
const container = document.createElement('div')
container.innerHTML = `
query {
user
`
const identifiers = [
createTestIdentifier('query', 1, 1),
createTestIdentifier('user', 3, 3),
]
const codeElement = container.querySelector('code')!
expect(() => {
calculator.prepareCodeBlock(codeElement, identifiers)
}).not.toThrow()
const wrappedElements = container.querySelectorAll('[data-graphql-id]')
expect(wrappedElements.length).toBe(2)
})
})
describe('createSimpleOverlay', () => {
it('should create positioned overlay element', () => {
const position = { top: 10, left: 20, width: 40, height: 20 }
const identifier = createTestIdentifier('user', 1, 1)
const overlay = createSimpleOverlay(position, identifier)
expect(overlay.style.position).toBe('absolute')
expect(overlay.style.top).toBe('10px')
expect(overlay.style.left).toBe('20px')
expect(overlay.style.width).toBe('40px')
expect(overlay.style.height).toBe('20px')
expect(overlay.style.cursor).toBe('pointer')
expect(overlay.getAttribute('data-graphql-overlay')).toBe('true')
expect(overlay.getAttribute('data-graphql-name')).toBe('user')
expect(overlay.getAttribute('data-graphql-kind')).toBe('Field')
})
it('should handle click events', () => {
const position = { top: 10, left: 20, width: 40, height: 20 }
const identifier = createTestIdentifier('user', 1, 1)
let clickedIdentifier: Identifier | null = null
const overlay = createSimpleOverlay(position, identifier, {
onClick: (id) => {
clickedIdentifier = id
},
})
// Simulate click
const event = new MouseEvent('click')
overlay.dispatchEvent(event)
expect(clickedIdentifier).toBe(identifier)
})
it('should handle hover events', () => {
const position = { top: 10, left: 20, width: 40, height: 20 }
const identifier = createTestIdentifier('user', 1, 1)
let hoveredIdentifier: Identifier | null = null
const overlay = createSimpleOverlay(position, identifier, {
onHover: (id) => {
hoveredIdentifier = id
},
})
// Simulate hover
const event = new MouseEvent('mouseenter')
overlay.dispatchEvent(event)
expect(hoveredIdentifier).toBe(identifier)
})
it('should apply custom className', () => {
const position = { top: 10, left: 20, width: 40, height: 20 }
const identifier = createTestIdentifier('user', 1, 1)
const overlay = createSimpleOverlay(position, identifier, {
className: 'custom-overlay-class',
})
expect(overlay.className).toBe('custom-overlay-class')
})
})
})