// @vitest-environment jsdom import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { afterEach, expect, it } from 'vitest'; import { installTableRowMarking } from '../../src/app/table-row-marking'; const styles = readFileSync(resolve('src/styles/components.css'), 'utf8'); const resultStyles = readFileSync(resolve('src/styles/datahub.css'), 'utf8'); const tokens = readFileSync(resolve('src/styles/ui-tokens.css'), 'utf8'); afterEach(() => { document.body.replaceChildren(); }); it('marks a clicked data row across the whole native table, including after horizontal scrolling', () => { const scroll = document.createElement('div'); scroll.innerHTML = '' + '
FirstLast
First ALast A
First BLast B
'; document.body.append(scroll); const stop = installTableRowMarking(document); const rows = scroll.querySelectorAll('tbody tr'); scroll.scrollLeft = 400; rows[0].lastElementChild!.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(rows[0].getAttribute('data-row-marked')).toBe('true'); expect(rows[1].hasAttribute('data-row-marked')).toBe(false); rows[1].lastElementChild!.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(rows[0].hasAttribute('data-row-marked')).toBe(false); expect(rows[1].getAttribute('data-row-marked')).toBe('true'); scroll.querySelector('thead th')!.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(rows[1].getAttribute('data-row-marked')).toBe('true'); stop(); expect(styles).toMatch(/table tbody tr\[data-row-marked="true"\] > :is\(th, td\)/); expect(styles).toContain('inset 0 0 0 9999px var(--table-row-marking-surface)'); expect(styles).toContain('inset 0 var(--bw-emphasis) 0 var(--table-row-marking)'); }); it('marks a Result DB row without preventing its existing expand action or affecting another table', () => { const area = document.createElement('div'); area.innerHTML = '
Header
' + '
' + '
' + '
Independent table
'; document.body.append(area); const stop = installTableRowMarking(document); const rows = area.querySelectorAll('[role="table"] [role="row"]'); const button = rows[1].querySelector('button')!; let expanded = false; button.addEventListener('click', () => { expanded = true; }); button.querySelector('span')!.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(expanded).toBe(true); expect(rows[1].getAttribute('data-row-marked')).toBe('true'); rows[2].querySelector('button')!.click(); expect(rows[1].hasAttribute('data-row-marked')).toBe(false); expect(rows[2].getAttribute('data-row-marked')).toBe('true'); area.querySelector('td')!.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(rows[2].getAttribute('data-row-marked')).toBe('true'); rows[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(rows[0].hasAttribute('data-row-marked')).toBe(false); stop(); expect(resultStyles).toContain('.results-browser-row[data-row-marked="true"]'); expect(resultStyles).toContain('background: var(--table-row-marking-surface)'); expect(resultStyles).toContain('inset 0 var(--bw-emphasis) 0 var(--table-row-marking)'); }); it('uses the blue table reading marker in both themes while keeping ordinary selection separate', () => { expect(tokens.match(/--table-row-marking: #(?:4CC2FF|0A66C2);/g)).toHaveLength(2); expect(tokens.match(/--table-row-marking-surface: color-mix\(in srgb, var\(--table-row-marking\) 14%, transparent\);/g)).toHaveLength(2); expect(tokens).toContain('--selection-border: var(--marking-color)'); expect(tokens).toContain('--focus-ring: #4CC2FF'); expect(tokens).toContain('--focus-ring: #0A66C2'); }); it('ignores virtual spacer rows and releases the listener on unmount', () => { const table = document.createElement('table'); table.innerHTML = 'SpacerValue'; document.body.append(table); const stop = installTableRowMarking(document); const rows = table.querySelectorAll('tr'); rows[0].querySelector('td')!.click(); expect(rows[0].hasAttribute('data-row-marked')).toBe(false); stop(); rows[1].querySelector('td')!.click(); expect(rows[1].hasAttribute('data-row-marked')).toBe(false); });