// @vitest-environment jsdom
import { afterEach, describe, expect, it } from 'vitest';
import { computeLocator, resolveLocator } from '../locator';
afterEach(() => {
document.body.innerHTML = '';
});
describe('computeLocator', () => {
it('builds an id selector when the element has a unique id', () => {
const el = document.createElement('button');
el.id = 'submit-btn';
el.textContent = 'Submit';
document.body.appendChild(el);
const locator = computeLocator(el, 'button');
expect(locator.selector).toBe('#submit-btn');
expect(locator.role).toBe('button');
expect(locator.name).toBe('Submit');
expect(locator.tag).toBe('button');
});
it('prefers data-testid over a structural path', () => {
const el = document.createElement('a');
el.setAttribute('data-testid', 'nav-home');
el.textContent = 'Home';
document.body.appendChild(el);
expect(computeLocator(el, 'link').selector).toBe('a[data-testid="nav-home"]');
});
it('falls back to a structural path when no stable attribute exists', () => {
document.body.innerHTML = '';
const second = document.body.querySelectorAll('a')[1] as HTMLElement;
const locator = computeLocator(second, 'link');
expect(locator.selector).toContain('nth-of-type');
// The structural path must re-find the same element.
expect(document.querySelector(locator.selector)).toBe(second);
});
});
describe('resolveLocator', () => {
it('resolves via selector when structure is intact', () => {
const el = document.createElement('button');
el.id = 'go';
el.textContent = 'Go';
document.body.appendChild(el);
const locator = computeLocator(el, 'button');
expect(resolveLocator(locator)).toBe(el);
});
it('resolves by role + name when the selector breaks', () => {
// Capture with a structural path (no stable attribute).
document.body.innerHTML = '
';
const original = document.body.querySelector('a') as HTMLElement;
const locator = computeLocator(original, 'link');
// Re-render shifts the element: extra wrapper + sibling inserted, so
// the captured nth-of-type path no longer matches it.
document.body.innerHTML =
'';
const resolved = resolveLocator(locator);
expect(resolved).not.toBeNull();
expect(resolved?.textContent).toBe('Browse Catalog');
});
it('returns null when the element is genuinely gone', () => {
const el = document.createElement('a');
el.textContent = 'Vanishing Link';
document.body.appendChild(el);
const locator = computeLocator(el, 'link');
document.body.innerHTML = '';
expect(resolveLocator(locator)).toBeNull();
});
it('matches an ARIA-roled element by role + name', () => {
document.body.innerHTML =
'≡
';
const original = document.body.firstElementChild as HTMLElement;
const locator = computeLocator(original, 'button');
// Recreate the node — same role + accessible name, new identity.
document.body.innerHTML =
'≡
';
const fresh = document.body.querySelectorAll('[role="button"]')[0];
expect(resolveLocator(locator)).toBe(fresh);
});
});