From 1424e24076ec1198a72d2ed54ac59e81abcbc63d Mon Sep 17 00:00:00 2001 From: raclim Date: Tue, 26 Nov 2024 12:25:51 -0500 Subject: [PATCH 1/7] missing dispatch import --- client/modules/IDE/components/ConsoleInput.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/modules/IDE/components/ConsoleInput.jsx b/client/modules/IDE/components/ConsoleInput.jsx index c026d29d4..daac76839 100644 --- a/client/modules/IDE/components/ConsoleInput.jsx +++ b/client/modules/IDE/components/ConsoleInput.jsx @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { useRef, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; import CodeMirror from 'codemirror'; import { Encode } from 'console-feed'; @@ -15,6 +16,7 @@ function ConsoleInput({ theme, fontSize }) { const [commandCursor, setCommandCursor] = useState(-1); const codemirrorContainer = useRef(null); const cmInstance = useRef(null); + const dispatch = useDispatch(); useEffect(() => { cmInstance.current = CodeMirror(codemirrorContainer.current, { @@ -45,7 +47,7 @@ function ConsoleInput({ theme, fontSize }) { payload: { source: 'console', messages } }); - dispatchConsoleEvent(consoleEvent); + dispatch(dispatchConsoleEvent(consoleEvent)); cm.setValue(''); setCommandHistory([value, ...commandHistory]); setCommandCursor(-1); From fc6fef3b1b4550405d3ed2e6f0dd7301625a5afe Mon Sep 17 00:00:00 2001 From: raclim Date: Wed, 27 Nov 2024 17:04:40 -0500 Subject: [PATCH 2/7] wrap actions with dispatch --- client/modules/IDE/components/FileNode.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index e589fb3cf..04d7223bd 100644 --- a/client/modules/IDE/components/FileNode.jsx +++ b/client/modules/IDE/components/FileNode.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import React, { useState, useRef } from 'react'; -import { connect } from 'react-redux'; +import { connect, useDispatch } from 'react-redux'; import { useTranslation } from 'react-i18next'; import * as IDEActions from '../actions/ide'; @@ -87,6 +87,7 @@ const FileNode = ({ const [isEditingName, setIsEditingName] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [updatedName, setUpdatedName] = useState(name); + const dispatch = useDispatch(); const { t } = useTranslation(); const fileNameInput = useRef(null); @@ -122,17 +123,17 @@ const FileNode = ({ }; const handleClickAddFile = () => { - newFile(id); + dispatch(newFile(id)); setTimeout(() => hideFileOptions(), 0); }; const handleClickAddFolder = () => { - newFolder(id); + dispatch(newFolder(id)); setTimeout(() => hideFileOptions(), 0); }; const handleClickUploadFile = () => { - openUploadFileModal(id); + dispatch(openUploadFileModal(id)); setTimeout(hideFileOptions, 0); }; From d26e9ddf260ab8e23577fd44fbf9791b00a68e07 Mon Sep 17 00:00:00 2001 From: raclim Date: Wed, 27 Nov 2024 18:46:21 -0500 Subject: [PATCH 3/7] update test to add useDispatch --- .../IDE/components/FileNode.unit.test.jsx | 64 ++++++++++++++----- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/client/modules/IDE/components/FileNode.unit.test.jsx b/client/modules/IDE/components/FileNode.unit.test.jsx index 8676a817d..86d350462 100644 --- a/client/modules/IDE/components/FileNode.unit.test.jsx +++ b/client/modules/IDE/components/FileNode.unit.test.jsx @@ -1,4 +1,7 @@ import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import { useDispatch } from 'react-redux'; import { fireEvent, @@ -9,7 +12,19 @@ import { } from '../../../test-utils'; import { FileNode } from './FileNode'; +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn() +})); + describe('', () => { + const mockDispatch = jest.fn(); + const mockStore = configureStore([]); + + beforeEach(() => { + useDispatch.mockReturnValue(mockDispatch); + }); + const changeName = (newFileName) => { const renameButton = screen.getByText(/Rename/i); fireEvent.click(renameButton); @@ -25,6 +40,19 @@ describe('', () => { }; const renderFileNode = (fileType, extraProps = {}) => { + const initialState = { + files: [ + { + id: '0', + name: fileType === 'folder' ? 'afolder' : 'test.jsx', + fileType + } + ], + user: { authenticated: false } + }; + + const store = mockStore(initialState); + const props = { ...extraProps, id: '0', @@ -45,24 +73,28 @@ describe('', () => { setProjectName: jest.fn() }; - render(); + render( + + + + ); - return props; + return { store, props }; }; describe('fileType: file', () => { it('cannot change to an empty name', async () => { - const props = renderFileNode('file'); + const { props } = renderFileNode('file'); changeName(''); - await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled()); + await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled()); await expectFileNameToBe(props.name); }); it('can change to a valid filename', async () => { const newName = 'newname.jsx'; - const props = renderFileNode('file'); + const { props } = renderFileNode('file'); changeName(newName); @@ -74,11 +106,11 @@ describe('', () => { it('must have an extension', async () => { const newName = 'newname'; - const props = renderFileNode('file'); + const { props } = renderFileNode('file'); changeName(newName); - await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled()); + await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled()); await expectFileNameToBe(props.name); }); @@ -87,7 +119,7 @@ describe('', () => { window.confirm = mockConfirm; const newName = 'newname.gif'; - const props = renderFileNode('file'); + const { props } = renderFileNode('file'); changeName(newName); @@ -95,33 +127,33 @@ describe('', () => { await waitFor(() => expect(props.updateFileName).toHaveBeenCalledWith(props.id, newName) ); - await expectFileNameToBe(props.name); + await expectFileNameToBe(newName); }); it('cannot be just an extension', async () => { const newName = '.jsx'; - const props = renderFileNode('file'); + const { props } = renderFileNode('file'); changeName(newName); - await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled()); + await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled()); await expectFileNameToBe(props.name); }); }); describe('fileType: folder', () => { it('cannot change to an empty name', async () => { - const props = renderFileNode('folder'); + const { props } = renderFileNode('folder'); changeName(''); - await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled()); + await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled()); await expectFileNameToBe(props.name); }); it('can change to another name', async () => { const newName = 'foldername'; - const props = renderFileNode('folder'); + const { props } = renderFileNode('folder'); changeName(newName); @@ -133,11 +165,11 @@ describe('', () => { it('cannot have a file extension', async () => { const newName = 'foldername.jsx'; - const props = renderFileNode('folder'); + const { props } = renderFileNode('folder'); changeName(newName); - await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled()); + await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled()); await expectFileNameToBe(props.name); }); }); From 0a321b3fd0e805c211e3430c39f999e06839270c Mon Sep 17 00:00:00 2001 From: raclim Date: Thu, 28 Nov 2024 10:06:18 -0500 Subject: [PATCH 4/7] update dispatch and add selectors --- client/modules/IDE/components/FileNode.jsx | 132 +++++---------------- 1 file changed, 31 insertions(+), 101 deletions(-) diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index 04d7223bd..cd6caa325 100644 --- a/client/modules/IDE/components/FileNode.jsx +++ b/client/modules/IDE/components/FileNode.jsx @@ -1,43 +1,17 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import React, { useState, useRef } from 'react'; -import { connect, useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import * as IDEActions from '../actions/ide'; import * as FileActions from '../actions/files'; +import parseFileName from '../utils/parseFileName'; import DownArrowIcon from '../../../images/down-filled-triangle.svg'; import FolderRightIcon from '../../../images/triangle-arrow-right.svg'; import FolderDownIcon from '../../../images/triangle-arrow-down.svg'; import FileTypeIcon from './FileTypeIcon'; -function parseFileName(name) { - const nameArray = name.split('.'); - if (nameArray.length > 1) { - const extension = `.${nameArray[nameArray.length - 1]}`; - const baseName = nameArray.slice(0, -1).join('.'); - const firstLetter = baseName[0]; - const lastLetter = baseName[baseName.length - 1]; - const middleText = baseName.slice(1, -1); - return { - baseName, - firstLetter, - lastLetter, - middleText, - extension - }; - } - const firstLetter = name[0]; - const lastLetter = name[name.length - 1]; - const middleText = name.slice(1, -1); - return { - baseName: name, - firstLetter, - lastLetter, - middleText - }; -} - function FileName({ name }) { const { baseName, @@ -62,41 +36,35 @@ FileName.propTypes = { name: PropTypes.string.isRequired }; -const FileNode = ({ - id, - parentId, - children, - name, - fileType, - isSelectedFile, - isFolderClosed, - setSelectedFile, - deleteFile, - updateFileName, - resetSelectedFile, - newFile, - newFolder, - showFolderChildren, - hideFolderChildren, - canEdit, - openUploadFileModal, - authenticated, - onClickFile -}) => { +const FileNode = ({ id, canEdit, onClickFile }) => { + const dispatch = useDispatch(); + const { t } = useTranslation(); + + const fileNode = + useSelector((state) => state.files.find((file) => file.id === id)) || {}; + const authenticated = useSelector((state) => state.user.authenticated); + + const { + name = '', + parentId = null, + children = [], + fileType = 'file', + isSelectedFile = false, + isFolderClosed = false + } = fileNode; + const [isOptionsOpen, setIsOptionsOpen] = useState(false); const [isEditingName, setIsEditingName] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [updatedName, setUpdatedName] = useState(name); - const dispatch = useDispatch(); - const { t } = useTranslation(); const fileNameInput = useRef(null); const fileOptionsRef = useRef(null); const handleFileClick = (event) => { event.stopPropagation(); if (name !== 'root' && !isDeleting) { - setSelectedFile(id); + dispatch(IDEActions.setSelectedFile(id)); } if (onClickFile) { onClickFile(); @@ -123,17 +91,17 @@ const FileNode = ({ }; const handleClickAddFile = () => { - dispatch(newFile(id)); + dispatch(IDEActions.newFile(id)); setTimeout(() => hideFileOptions(), 0); }; const handleClickAddFolder = () => { - dispatch(newFolder(id)); + dispatch(IDEActions.newFolder(id)); setTimeout(() => hideFileOptions(), 0); }; const handleClickUploadFile = () => { - dispatch(openUploadFileModal(id)); + dispatch(IDEActions.openUploadFileModal(id)); setTimeout(hideFileOptions, 0); }; @@ -142,8 +110,8 @@ const FileNode = ({ if (window.confirm(prompt)) { setIsDeleting(true); - resetSelectedFile(id); - setTimeout(() => deleteFile(id, parentId), 100); + dispatch(IDEActions.resetSelectedFile(id)); + setTimeout(() => dispatch(FileActions.deleteFile(id, parentId), 100)); } }; @@ -159,7 +127,7 @@ const FileNode = ({ const saveUpdatedFileName = () => { if (updatedName !== name) { - updateFileName(id, updatedName); + dispatch(FileActions.updateFileName(id, updatedName)); } }; @@ -244,7 +212,7 @@ const FileNode = ({
- + );