5 Commits

Author SHA1 Message Date
d9f1a16d94 Merge pull request #96 from lordmathis/chore/update-deps
Bump React and TypeScript types
2025-11-15 15:21:49 +01:00
0999fa9315 Fix FileTree tests with async rendering and mock resize observer 2025-11-15 15:16:19 +01:00
cc8c8fd414 Fix typescript issues 2025-11-15 14:56:48 +01:00
9ba37b3342 Update react dependencies 2025-11-15 14:47:13 +01:00
140ccd6879 Bump React and TypeScript types 2025-11-15 01:19:01 +01:00
5 changed files with 93 additions and 73 deletions

76
app/package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.6",
"@floating-ui/react": "^0.27.16",
"@mantine/core": "^8.3.7",
"@mantine/hooks": "^8.3.7",
"@mantine/modals": "^8.3.7",
@@ -22,9 +23,9 @@
"@react-hook/resize-observer": "^2.0.2",
"@tabler/icons-react": "^3.35.0",
"codemirror": "^6.0.2",
"react": "^18.3.1",
"react": "^19.2.0",
"react-arborist": "^3.4.3",
"react-dom": "^18.3.1",
"react-dom": "^19.2.0",
"rehype-highlight": "^7.0.2",
"rehype-mathjax": "^7.1.0",
"rehype-react": "^8.0.0",
@@ -41,8 +42,8 @@
"@testing-library/react": "^16.3.0",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.10.1",
"@types/react": "^18.3.20",
"@types/react-dom": "^18.3.6",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.32.1",
"@vitejs/plugin-react": "^5.1.1",
@@ -2558,32 +2559,24 @@
"undici-types": "~7.16.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.20",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz",
"integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==",
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz",
"integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.6.tgz",
"integrity": "sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==",
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
"@types/react": "^19.2.0"
}
},
"node_modules/@types/unist": {
@@ -5808,9 +5801,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7426,13 +7419,10 @@
"license": "MIT"
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
@@ -7494,16 +7484,15 @@
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^18.3.1"
"react": "^19.2.0"
}
},
"node_modules/react-is": {
@@ -8089,13 +8078,10 @@
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
@@ -9157,12 +9143,12 @@
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {

View File

@@ -35,6 +35,7 @@
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.6",
"@floating-ui/react": "^0.27.16",
"@mantine/core": "^8.3.7",
"@mantine/hooks": "^8.3.7",
"@mantine/modals": "^8.3.7",
@@ -42,9 +43,9 @@
"@react-hook/resize-observer": "^2.0.2",
"@tabler/icons-react": "^3.35.0",
"codemirror": "^6.0.2",
"react": "^18.3.1",
"react": "^19.2.0",
"react-arborist": "^3.4.3",
"react-dom": "^18.3.1",
"react-dom": "^19.2.0",
"rehype-highlight": "^7.0.2",
"rehype-mathjax": "^7.1.0",
"rehype-react": "^8.0.0",
@@ -61,8 +62,8 @@
"@testing-library/react": "^16.3.0",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.10.1",
"@types/react": "^18.3.20",
"@types/react-dom": "^18.3.6",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.32.1",
"@vitejs/plugin-react": "^5.1.1",

View File

@@ -69,7 +69,19 @@ vi.mock('react-arborist', () => ({
// Mock resize observer hook
vi.mock('@react-hook/resize-observer', () => ({
default: vi.fn(),
default: vi.fn(
(
_target: unknown,
callback: (entry: { contentRect: { width: number; height: number } }) => void
) => {
// Immediately call the callback with a mock entry to provide size
if (callback) {
setTimeout(() => {
callback({ contentRect: { width: 300, height: 600 } });
}, 0);
}
}
),
}));
// Mock contexts
@@ -172,7 +184,7 @@ describe('FileTree', () => {
vi.clearAllMocks();
});
it('renders file tree with files', () => {
it('renders file tree with files', async () => {
const { getByTestId } = render(
<TestWrapper>
<FileTree
@@ -184,7 +196,9 @@ describe('FileTree', () => {
</TestWrapper>
);
expect(getByTestId('file-tree')).toBeInTheDocument();
await waitFor(() => {
expect(getByTestId('file-tree')).toBeInTheDocument();
});
expect(getByTestId('file-node-1')).toBeInTheDocument();
expect(getByTestId('file-node-2')).toBeInTheDocument();
});
@@ -201,6 +215,10 @@ describe('FileTree', () => {
</TestWrapper>
);
await waitFor(() => {
expect(getByTestId('file-node-1')).toBeInTheDocument();
});
const fileNode = getByTestId('file-node-1');
fireEvent.click(fileNode);
@@ -209,7 +227,7 @@ describe('FileTree', () => {
});
});
it('filters out hidden files when showHiddenFiles is false', () => {
it('filters out hidden files when showHiddenFiles is false', async () => {
const { getByTestId, queryByTestId } = render(
<TestWrapper>
<FileTree
@@ -221,6 +239,10 @@ describe('FileTree', () => {
</TestWrapper>
);
await waitFor(() => {
expect(getByTestId('file-node-1')).toBeInTheDocument();
});
// Should show regular files
expect(getByTestId('file-node-1')).toBeInTheDocument();
expect(getByTestId('file-node-2')).toBeInTheDocument();
@@ -229,7 +251,7 @@ describe('FileTree', () => {
expect(queryByTestId('file-node-4')).not.toBeInTheDocument();
});
it('shows hidden files when showHiddenFiles is true', () => {
it('shows hidden files when showHiddenFiles is true', async () => {
const { getByTestId } = render(
<TestWrapper>
<FileTree
@@ -241,13 +263,17 @@ describe('FileTree', () => {
</TestWrapper>
);
await waitFor(() => {
expect(getByTestId('file-node-1')).toBeInTheDocument();
});
// Should show all files including hidden
expect(getByTestId('file-node-1')).toBeInTheDocument();
expect(getByTestId('file-node-2')).toBeInTheDocument();
expect(getByTestId('file-node-4')).toBeInTheDocument();
});
it('renders empty tree when no files provided', () => {
it('renders empty tree when no files provided', async () => {
const { getByTestId } = render(
<TestWrapper>
<FileTree
@@ -259,6 +285,10 @@ describe('FileTree', () => {
</TestWrapper>
);
await waitFor(() => {
expect(getByTestId('file-tree')).toBeInTheDocument();
});
const tree = getByTestId('file-tree');
expect(tree).toBeInTheDocument();
expect(tree.children).toHaveLength(0);
@@ -276,6 +306,10 @@ describe('FileTree', () => {
</TestWrapper>
);
await waitFor(() => {
expect(getByTestId('file-node-2')).toBeInTheDocument();
});
// Click on folder (has children)
const folderNode = getByTestId('file-node-2');
fireEvent.click(folderNode);

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState, useLayoutEffect, useCallback } from 'react';
import React, { useRef, useState, useCallback } from 'react';
import { Tree, type NodeApi } from 'react-arborist';
import {
IconFile,
@@ -23,15 +23,11 @@ interface FileTreeProps {
loadFileList: () => Promise<void>;
}
const useSize = (target: React.RefObject<HTMLElement>): Size | undefined => {
const useSize = (
target: React.RefObject<HTMLElement | null>
): Size | undefined => {
const [size, setSize] = useState<Size>();
useLayoutEffect(() => {
if (target.current) {
setSize(target.current.getBoundingClientRect());
}
}, [target]);
useResizeObserver(target, (entry) => setSize(entry.contentRect));
return size;
};

View File

@@ -1,4 +1,4 @@
import React, { useRef, useLayoutEffect, useState } from 'react';
import React, { useRef, useState } from 'react';
import { Box } from '@mantine/core';
import { Tree, type NodeApi } from 'react-arborist';
import {
@@ -21,15 +21,11 @@ interface Size {
height: number;
}
const useSize = (target: React.RefObject<HTMLElement>): Size | undefined => {
const useSize = (
target: React.RefObject<HTMLElement | null>
): Size | undefined => {
const [size, setSize] = useState<Size>();
useLayoutEffect(() => {
if (target.current) {
setSize(target.current.getBoundingClientRect());
}
}, [target]);
useResizeObserver(target, (entry) => setSize(entry.contentRect));
return size;
};
@@ -239,7 +235,10 @@ export const FolderSelector: React.FC<FolderSelectorProps> = ({
}}
>
{/* Root option */}
<RootNode isSelected={selectedPath === ''} onSelect={() => onSelect('')} />
<RootNode
isSelected={selectedPath === ''}
onSelect={() => onSelect('')}
/>
{/* Folder tree */}
{size && folders.length > 0 && (
@@ -255,7 +254,11 @@ export const FolderSelector: React.FC<FolderSelectorProps> = ({
disableDrop={() => true}
>
{(props) => (
<FolderNode {...props} selectedPath={selectedPath} onSelect={onSelect} />
<FolderNode
{...props}
selectedPath={selectedPath}
onSelect={onSelect}
/>
)}
</Tree>
)}