import assert from 'node:assert/strict'; import test from 'node:test'; import { findNextEditableColumnId, findNextEditableRowId } from './cellNavigation.js'; // AURORA-1891: Tab/Shift+Tab (в строке) и Enter (по столбцу, вниз/вверх) — // EditCell.jsx/VspDropdownEditCell.jsx знают только текущую строку/колонку, // а сам обход видимых листовых колонок (getOrderedVisibleColumns) и строк // (table.getRowModel().flatRows) и предикат редактируемости (isCellEditable) // собирает вызывающая сторона (RealtimeTable.jsx meta.openNextEditableCell). // Здесь — только чистый обход уже готового списка. // columns — то, что вернул бы getOrderedVisibleColumns: скрытая колонка // конфига сюда вообще не попала бы, поэтому «пропуск скрытых» проверяется // самим фактом, что функция работает только с тем, что ей передали, и про // скрытые колонки ничего не знает. const COLUMNS = [ { id: 'a', editable: true }, { id: 'b', editable: false }, // не редактируется в этой строке (напр. вычисляемая) { id: 'c', editable: true }, { id: 'd', editable: false }, ]; const isColumnEditable = (column) => column.editable; test('Tab: moves right to the next editable column, skipping non-editable ones', () => { const next = findNextEditableColumnId({ columns: COLUMNS, currentColumnId: 'a', step: 1, isEditable: isColumnEditable }); assert.equal(next, 'c'); }); test('Shift+Tab: moves left to the previous editable column', () => { const prev = findNextEditableColumnId({ columns: COLUMNS, currentColumnId: 'c', step: -1, isEditable: isColumnEditable }); assert.equal(prev, 'a'); }); test('Tab at the end of the row returns null (конец строки)', () => { const next = findNextEditableColumnId({ columns: COLUMNS, currentColumnId: 'c', step: 1, isEditable: isColumnEditable }); assert.equal(next, null); }); test('Shift+Tab at the start of the row returns null', () => { const prev = findNextEditableColumnId({ columns: COLUMNS, currentColumnId: 'a', step: -1, isEditable: isColumnEditable }); assert.equal(prev, null); }); test('an unknown current column id returns null instead of throwing', () => { const next = findNextEditableColumnId({ columns: COLUMNS, currentColumnId: 'missing', step: 1, isEditable: isColumnEditable }); assert.equal(next, null); }); // rows — table.getRowModel().flatRows: включают итоговые строки (GROUP/ITEM/ // ROOT), которые isCellEditable делает нередактируемыми независимо от // колонки (rowType !== 'INPUT'); здесь это просто row.editable === false. const ROWS = [ { id: 'row-1', editable: true }, { id: 'row-2', editable: false }, // итоговая строка (GROUP) { id: 'row-3', editable: true }, { id: 'row-4', editable: false }, // итоговая строка (ROOT) ]; const isRowEditable = (row) => row.editable; test('Enter: moves down to the next row where the column is editable, skipping summary rows', () => { const next = findNextEditableRowId({ rows: ROWS, currentRowId: 'row-1', step: 1, isEditable: isRowEditable }); assert.equal(next, 'row-3'); }); test('Enter at the last row returns null (конец таблицы)', () => { const next = findNextEditableRowId({ rows: ROWS, currentRowId: 'row-3', step: 1, isEditable: isRowEditable }); assert.equal(next, null); }); test('moving up (step: -1) finds the previous editable row', () => { const prev = findNextEditableRowId({ rows: ROWS, currentRowId: 'row-3', step: -1, isEditable: isRowEditable }); assert.equal(prev, 'row-1'); }); test('an unknown current row id returns null instead of throwing', () => { const next = findNextEditableRowId({ rows: ROWS, currentRowId: 'missing', step: 1, isEditable: isRowEditable }); assert.equal(next, null); });