diff --git a/frontend/src/pages/nodes/NodeList.tsx b/frontend/src/pages/nodes/NodeList.tsx index 0b850af24..aec149054 100644 --- a/frontend/src/pages/nodes/NodeList.tsx +++ b/frontend/src/pages/nodes/NodeList.tsx @@ -650,7 +650,7 @@ export default function NodeList({ loading={loading} scroll={{ x: 'max-content' }} size="middle" - rowKey="id" + rowKey="key" rowSelection={dataSource.length > 1 ? { selectedRowKeys: selectedIds, onChange: (keys) => onSelectionChange(keys.filter((k) => typeof k === 'number') as number[]), diff --git a/frontend/src/test/node-list-rowkey.test.tsx b/frontend/src/test/node-list-rowkey.test.tsx new file mode 100644 index 000000000..dae7a71c3 --- /dev/null +++ b/frontend/src/test/node-list-rowkey.test.tsx @@ -0,0 +1,46 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; + +import NodeList from '@/pages/nodes/NodeList'; +import type { NodeRecord } from '@/schemas/node'; + +import { renderWithProviders } from './test-utils'; + +const noop = () => {}; + +function sampleNodes(): NodeRecord[] { + return [ + { id: 1, name: 'parent', guid: 'p1', transitive: false, enable: true, status: 'online' }, + { id: 0, name: 'child-a', guid: 'ca', parentGuid: 'p1', transitive: true }, + { id: 0, name: 'child-b', guid: 'cb', parentGuid: 'p1', transitive: true }, + ]; +} + +describe('NodeList desktop table row keys', () => { + afterEach(() => vi.restoreAllMocks()); + + it('gives transitive sub-node rows distinct keys instead of colliding on id 0', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + renderWithProviders( + , + ); + + const duplicateKeyWarning = errorSpy.mock.calls.some((call) => + call.some((arg) => typeof arg === 'string' && arg.includes('same key')), + ); + expect(duplicateKeyWarning).toBe(false); + }); +});