import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest' import SelectorEngine from '../../../src/bootstrap/dom/selector-engine' import { clearFixture, getFixture } from '../../helpers/fixture' vi.mock('../../../src/bootstrap/util/index', async (importOriginal) => { const actual = await importOriginal() return { ...actual, isVisible: () => true, } }) describe('SelectorEngine', () => { let fixtureEl: HTMLElement beforeAll(() => { fixtureEl = getFixture() }) afterEach(() => { clearFixture() }) describe('find', () => { beforeEach(() => { fixtureEl.innerHTML = '
' }) it('should return an array of matched elements', () => { const result = SelectorEngine.find('span', fixtureEl) expect(result).toHaveLength(2) expect(result[0].classList.contains('a')).toBe(true) expect(result[1].classList.contains('b')).toBe(true) }) it('should return an empty array when nothing matches', () => { expect(SelectorEngine.find('.missing', fixtureEl)).toEqual([]) }) it('should default to document.documentElement', () => { const result = SelectorEngine.find(`#${fixtureEl.id} span`) expect(result).toHaveLength(2) }) }) describe('findOne', () => { beforeEach(() => { fixtureEl.innerHTML = '
' }) it('should return the first matched element', () => { const result = SelectorEngine.findOne('span', fixtureEl) expect(result).not.toBeNull() expect(result!.classList.contains('first')).toBe(true) }) it('should return null when nothing matches', () => { expect(SelectorEngine.findOne('.missing', fixtureEl)).toBeNull() }) }) describe('children', () => { beforeEach(() => { fixtureEl.innerHTML = '

' }) it('should return only direct children matching the selector', () => { const parent = fixtureEl.querySelector('div')! const result = SelectorEngine.children(parent, 'span') expect(result).toHaveLength(2) result.forEach((el) => expect(el.tagName).toBe('SPAN')) }) it('should return an empty array when no children match', () => { const parent = fixtureEl.querySelector('div')! expect(SelectorEngine.children(parent, 'button')).toEqual([]) }) }) describe('parents', () => { beforeEach(() => { fixtureEl.innerHTML = '
' }) it('should return all ancestor elements matching the selector', () => { const target = fixtureEl.querySelector('#target')! const result = SelectorEngine.parents(target as HTMLElement, 'div') expect(result.length).toBeGreaterThanOrEqual(2) expect(result[0].classList.contains('inner')).toBe(true) expect(result[1].classList.contains('outer')).toBe(true) }) it('should return an empty array when no ancestors match', () => { const target = fixtureEl.querySelector('#target')! expect(SelectorEngine.parents(target as HTMLElement, 'table')).toEqual([]) }) }) describe('prev', () => { beforeEach(() => { fixtureEl.innerHTML = '

' }) it('should return the previous sibling matching the selector', () => { const el = fixtureEl.querySelector('.c')! as HTMLElement const result = SelectorEngine.prev(el, 'span') expect(result).toHaveLength(1) expect(result[0].classList.contains('a')).toBe(true) }) it('should return an empty array if no previous sibling matches', () => { const el = fixtureEl.querySelector('.a')! as HTMLElement expect(SelectorEngine.prev(el, 'button')).toEqual([]) }) it('should skip non-matching siblings', () => { const el = fixtureEl.querySelector('.c')! as HTMLElement const result = SelectorEngine.prev(el, 'p') expect(result).toHaveLength(1) expect(result[0].classList.contains('b')).toBe(true) }) }) describe('next', () => { beforeEach(() => { fixtureEl.innerHTML = '

' }) it('should return the next sibling matching the selector', () => { const el = fixtureEl.querySelector('.a')! as HTMLElement const result = SelectorEngine.next(el, 'span') expect(result).toHaveLength(1) expect(result[0].classList.contains('c')).toBe(true) }) it('should return an empty array if no next sibling matches', () => { const el = fixtureEl.querySelector('.c')! as HTMLElement expect(SelectorEngine.next(el, 'button')).toEqual([]) }) it('should skip non-matching siblings', () => { const el = fixtureEl.querySelector('.a')! as HTMLElement const result = SelectorEngine.next(el, 'p') expect(result).toHaveLength(1) expect(result[0].classList.contains('b')).toBe(true) }) }) describe('focusableChildren', () => { it('should return focusable children', () => { fixtureEl.innerHTML = '
text
' const parent = fixtureEl.querySelector('div')! const result = SelectorEngine.focusableChildren(parent) expect(result.length).toBeGreaterThanOrEqual(1) const tags = result.map((el) => el.tagName) expect(tags).toContain('BUTTON') expect(tags).toContain('INPUT') expect(tags).not.toContain('SPAN') }) it('should exclude disabled elements', () => { fixtureEl.innerHTML = '
' const parent = fixtureEl.querySelector('div')! const result = SelectorEngine.focusableChildren(parent) const texts = result.map((el) => el.textContent) expect(texts).toContain('Yes') expect(texts).not.toContain('No') }) it('should exclude elements with negative tabindex', () => { fixtureEl.innerHTML = '
' const parent = fixtureEl.querySelector('div')! const result = SelectorEngine.focusableChildren(parent) const texts = result.map((el) => el.textContent) expect(texts).toContain('Visible') expect(texts).not.toContain('Hidden') }) }) describe('getSelector / getSelectorFromElement / getElementFromSelector', () => { it('should resolve data-tblr-target', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBe(fixtureEl.querySelector('#target')) }) it('should resolve data-bs-target as fallback', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBe(fixtureEl.querySelector('#target')) }) it('should prioritize data-tblr-target over data-bs-target', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)!.id).toBe('tblr') }) it('should resolve href as fallback', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBe(fixtureEl.querySelector('#target')) }) it('should return null when no selector can be resolved', () => { fixtureEl.innerHTML = '' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBeNull() }) it('should return null for href without hash or dot', () => { fixtureEl.innerHTML = '' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBeNull() }) it('should return null for href="#"', () => { fixtureEl.innerHTML = '' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBeNull() }) it('should extract hash from full URL href', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBe(fixtureEl.querySelector('#section')) }) it('should resolve class-based href selectors', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getElementFromSelector(trigger)).toBe(fixtureEl.querySelector('.target')) }) it('getSelectorFromElement should return selector string when element exists', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getSelectorFromElement(trigger)).toBe('#target') }) it('getSelectorFromElement should return null when target element does not exist', () => { fixtureEl.innerHTML = '' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getSelectorFromElement(trigger)).toBeNull() }) it('getSelectorFromElement should return null when no selector', () => { fixtureEl.innerHTML = '' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getSelectorFromElement(trigger)).toBeNull() }) }) describe('getMultipleElementsFromSelector', () => { it('should return all matching elements', () => { fixtureEl.innerHTML = '
' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getMultipleElementsFromSelector(trigger)).toHaveLength(2) }) it('should return an empty array when no selector', () => { fixtureEl.innerHTML = '' const trigger = fixtureEl.querySelector('a')! as HTMLElement expect(SelectorEngine.getMultipleElementsFromSelector(trigger)).toEqual([]) }) }) })