- processColumns (tableColumns.jsx): enableEditing для каждой листовой колонки через isCellEditable (PR #281), а не только для vsp_dropdown — MRT больше не открывает редактор (даже disabled) на нередактируемой ячейке, сервер не берёт блокировку, коллеги не видят 🔒. - Одиночный клик по редактируемой, не заблокированной ячейке открывает редактор (muiTableBodyCellProps.onClick, RealtimeTable.jsx); клик по нередактируемой/занятой ячейке, как раньше, выделяет строку. Удалён мёртвый onClick/handleClick из Cell.jsx. - Клавиатура на выделенной ячейке: Enter/F2 — открыть редактор; Delete/Backspace — открыть с пустым значением; печатный символ — открыть и подставить его вместо текущего значения (хранится в state RealtimeTable, читается через table.options.meta.getEditorInitialInput). - EditCell.jsx/VspDropdownEditCell.jsx: Esc — отмена без cell_updated; Tab/ Shift+Tab и Enter — сохранить и перейти к следующей/предыдущей редактируемой ячейке через table.options.meta.openNextEditableCell → utils/cellNavigation.js (чистые findNextEditableColumnId/findNextEditableRowId по getOrderedVisibleColumns и table.getRowModel().flatRows). - utils/formulaUtils.js: isSameCellValue — handleSave не шлёт cell_updated, если значение не изменилось (число/строка, «1 500,0», пусто/null); формула всегда считается изменённой. - Фокус: setTimeout(50) → requestAnimationFrame; после закрытия редактора фокус возвращается в <td> ячейки. Тесты: utils/cellNavigation.test.js, utils/formulaUtils.test.js (новые, 15 тестов). node --test src/components/RealtimeTable/utils/*.test.js: 85 тестов, 84 passed, 1 известный pre-existing fail (columnLayoutUtils.test.js, нет node_modules в этом worktree, см. PR #281/#283). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
79 lines
4.1 KiB
JavaScript
79 lines
4.1 KiB
JavaScript
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);
|
||
});
|