diff --git a/__mocks__/File.context.test.js b/__mocks__/File.context.test.js
new file mode 100644
index 00000000..204d3d7e
--- /dev/null
+++ b/__mocks__/File.context.test.js
@@ -0,0 +1,43 @@
+import React, {useContext} from "react";
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { AuthenticationContextProvider, FileContextProvider, RepositoryContextProvider } from "../src";
+import { FileContext } from '../src/components/file/File.context';
+
+
+
+const FileContextCustomer = () => {
+ const fileContextValues = useContext(FileContext);
+ const fileContextKeysArray = Object.keys(fileContextValues);
+
+ return(
+
+ {
+ fileContextKeysArray.map((key)=>(`${key}/`))
+ }
+
+ )
+}
+
+
+
+
+describe('FileContextProvider', () => {
+ test('FileContextProvider renders correctly', () => {
+ render(
+
+ {}}
+ >
+
+
+
+
+
+ );
+ const test = screen.getByTestId('test');
+ expect(test).toHaveTextContent('state/stateValues/actions/component/components/config');
+ });
+})
\ No newline at end of file
diff --git a/__mocks__/FileCard.test.js b/__mocks__/FileCard.test.js
new file mode 100644
index 00000000..d46152c0
--- /dev/null
+++ b/__mocks__/FileCard.test.js
@@ -0,0 +1,270 @@
+import React from "react";
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import FileCard from '../src/components/file/FileCard';
+import { checkProps } from './testUtils';
+
+
+
+
+jest.mock('markdown-translatable/dist/components/block-editable/BlockEditable', () =>
+({markdown, onEdit, editable}) => (
+ {
+ if(editable){
+ onEdit(event.target.value)
+ }
+ }}
+ readOnly
+ />
+));
+
+
+
+
+const defaultProps = {
+ repository: {
+ owner: {
+ username: 'username',
+ avatar_url: 'avatar_url',
+ },
+ name: 'name',
+ avatar_url: 'avatar_url',
+ permissions: {
+ push: true,
+ },
+ full_name: 'full_name',
+ default_branch: 'default_branch'
+ },
+ file:{
+ name: 'file.name',
+ path: 'file.path',
+ sha: 'file.sha',
+ content: 'file.content',
+ branch: 'file.branch',
+ filepath: 'filepath',
+ save: () => {},
+ dangerouslyDelete: () => {},
+ close: () => {}
+ },
+ isAuthenticated: true
+}
+
+
+test('FileForm PropTypes', () => {
+ const conformingProps = {
+ repository: {
+ owner: {
+ username: 'username',
+ avatar_url: 'avatar_url',
+ },
+ name: 'name',
+ avatar_url: 'avatar_url',
+ permissions: {
+ push: true,
+ },
+ full_name: 'full_name',
+ default_branch: 'default_branch'
+ },
+ file:{
+ name: 'file.name',
+ path: 'file.path',
+ sha: 'file.sha',
+ content: 'file.content',
+ branch: 'file.branch',
+ filepath: 'filepath',
+ save: () => {},
+ dangerouslyDelete: () => {},
+ close: () => {}
+ },
+ isAuthenticated: true,
+ }
+ checkProps(FileCard, conformingProps);
+});
+
+describe('cardHeader',() => {
+ test('cardHeader is inside the document and visible', () => {
+ render();
+ const cardHeader = screen.getByTestId('cardHeader');
+ expect(cardHeader).toBeInTheDocument();
+ expect(cardHeader).toBeVisible();
+ });
+})
+
+describe('blockEditable', () => {
+ test('blockEditable is inside the document', () => {
+ render();
+ const blockEditable = screen.getByTestId('blockEditable');
+ expect(blockEditable).toBeInTheDocument();
+ });
+
+ test('blockEditable is editable when user is authenticated', () => {
+ render();
+ const blockEditable = screen.getByTestId('blockEditable');
+ fireEvent.change(blockEditable, {target: {value: 'changed text'}});
+ expect(blockEditable.value).toBe('changed text');
+ });
+
+ test('blockEditable is not editable when user is not authenticated', () => {
+ const updatedProps = {...defaultProps, isAuthenticated: false}
+ render();
+ const blockEditable = screen.getByTestId('blockEditable');
+ fireEvent.change(blockEditable, {target: {value: 'changed text'}});
+ expect(blockEditable.value).toBe(defaultProps.file.content);
+ });
+})
+
+describe('previewButton', () => {
+ test('previewButton is inside the document and visible', () => {
+ render();
+ const previewButton = screen.getByTestId('previewButton');
+ expect(previewButton).toBeInTheDocument();
+ expect(previewButton).toBeVisible();
+ });
+})
+
+describe('previewIcon', () => {
+ test('Initially previewIconOutlined is inside the document and visible and previewIcon is not', () => {
+ render();
+ const previewIconOutlined = screen.queryByTestId('previewIconOutlined');
+ expect(previewIconOutlined).toBeInTheDocument();
+ expect(previewIconOutlined).toBeVisible();
+ const previewIcon = screen.queryByTestId('previewIcon');
+ expect(previewIcon).toBeNull();
+ });
+
+ test('switch from previewIconOutlined to previewIcon and vice versa', () => {
+ render();
+ const previewButton = screen.getByTestId('previewButton');
+ fireEvent.click(previewButton)
+ let previewIconOutlined = screen.queryByTestId('previewIconOutlined');
+ expect(previewIconOutlined).toBeNull();
+ let previewIcon = screen.queryByTestId('previewIcon');
+ expect(previewIcon).toBeInTheDocument();
+ expect(previewIcon).toBeVisible();
+ fireEvent.click(previewButton)
+ previewIconOutlined = screen.queryByTestId('previewIconOutlined');
+ expect(previewIconOutlined).toBeInTheDocument();
+ expect(previewIconOutlined).toBeVisible();
+ previewIcon = screen.queryByTestId('previewIcon');
+ expect(previewIcon).toBeNull();
+ });
+})
+
+describe('saveButton', () => {
+ test('saveButton is inside the document and visible and disabled initialy', () => {
+ render();
+ const saveButton = screen.getByTestId('saveButton');
+ expect(saveButton).toBeInTheDocument();
+ expect(saveButton).toBeVisible();
+ expect(saveButton).toBeDisabled();
+ });
+
+ test('switch saveButton from enable to disable and vice versa', () => {
+ render();
+ const blockEditable = screen.getByTestId('blockEditable');
+ fireEvent.change(blockEditable, {target: {value: 'changed text'}});
+ let saveButton = screen.getByTestId('saveButton');
+ expect(saveButton).toBeEnabled();
+ fireEvent.change(blockEditable, {target: {value: defaultProps.file.content}});
+ saveButton = screen.getByTestId('saveButton');
+ expect(saveButton).toBeDisabled();
+ });
+
+ test('saveButton is enabled when the text change and the push permission is granted', () => {
+ render();
+ const blockEditable = screen.getByTestId('blockEditable');
+ fireEvent.change(blockEditable, {target: {value: 'changed text'}});
+ const saveButton = screen.getByTestId('saveButton');
+ expect(saveButton).toBeEnabled();
+ });
+
+ test('saveButton is disabled whenpush permission is not granted event it the text change or not', () => {
+ const updatedProps = {
+ ...defaultProps,
+ repository: {...defaultProps.repository,
+ permissions: {
+ ...defaultProps.repository.permissions, push: false
+ }
+ }
+ }
+ render();
+ const saveButton = screen.getByTestId('saveButton');
+ expect(saveButton).toBeDisabled();
+ const blockEditable = screen.getByTestId('blockEditable');
+ fireEvent.change(blockEditable, {target: {value: 'changed text'}});
+ expect(saveButton).toBeDisabled();
+ });
+
+
+})
+
+describe('saveIcon', () => {
+ test('Initially saveIconOutlined is inside the document and visible and saveIcon is not', () => {
+ render();
+ const saveIconOutlined = screen.queryByTestId('saveIconOutlined');
+ expect(saveIconOutlined).toBeInTheDocument();
+ expect(saveIconOutlined).toBeVisible();
+ const saveIcon = screen.queryByTestId('saveIcon');
+ expect(saveIcon).toBeNull();
+ });
+
+ test('switch from saveIconOutlined to saveIcon and vice versa', () => {
+ render();
+ const blockEditable = screen.getByTestId('blockEditable');
+ fireEvent.change(blockEditable, {target: {value: 'changed text'}});
+ let saveIconOutlined = screen.queryByTestId('saveIconOutlined');
+ expect(saveIconOutlined).toBeNull();
+ let saveIcon = screen.queryByTestId('saveIcon');
+ expect(saveIcon).toBeInTheDocument();
+ expect(saveIcon).toBeVisible();
+ fireEvent.change(blockEditable, {target: {value: defaultProps.file.content}});
+ saveIconOutlined = screen.queryByTestId('saveIconOutlined');
+ expect(saveIconOutlined).toBeInTheDocument();
+ expect(saveIconOutlined).toBeVisible();
+ saveIcon = screen.queryByTestId('saveIcon');
+ expect(saveIcon).toBeNull();
+ });
+})
+
+
+describe('Delete button', () => {
+ test('Delete button is inside the document and visible', () => {
+ render();
+ const deleteButton = screen.queryByTestId('deleteButton');
+ expect(deleteButton).toBeInTheDocument();
+ expect(deleteButton).toBeVisible();
+ });
+
+ test('Delete button is enabled when push permissions is granted', () => {
+ render();
+ const deleteButton = screen.queryByTestId('deleteButton');
+ expect(deleteButton).toBeEnabled();
+ });
+
+ test('Delete button is disabled when push permissions is not granted', () => {
+ const updatedProps = {
+ ...defaultProps,
+ repository: {...defaultProps.repository,
+ permissions: {
+ ...defaultProps.repository.permissions, push: false
+ }
+ }
+ }
+ render();
+ const deleteButton = screen.queryByTestId('deleteButton');
+ expect(deleteButton).toBeDisabled();
+ });
+})
+
+describe('closeButton', () => {
+ test('closeButton is inside the document and visible and enabled', () => {
+ render();
+ const closeButton = screen.queryByTestId('closeButton');
+ expect(closeButton).toBeInTheDocument();
+ expect(closeButton).toBeVisible();
+ expect(closeButton).toBeEnabled();
+ });
+})
diff --git a/__mocks__/FileForm.test.js b/__mocks__/FileForm.test.js
new file mode 100644
index 00000000..d52179df
--- /dev/null
+++ b/__mocks__/FileForm.test.js
@@ -0,0 +1,93 @@
+import React from "react";
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import FileForm from '../src/components/file/FileForm';
+import { checkProps } from './testUtils';
+
+const defaultProps = { onSubmit: () => {} }
+
+// render tests
+describe('TextFields',() => {
+ test('TextFields are inside the document', () => {
+ render();
+ const branchTextField = screen.getByTestId('branch-textField');
+ expect(branchTextField).toBeInTheDocument();
+
+ const filepathTextField = screen.getByTestId('filepath-textField');
+ expect(filepathTextField).toBeInTheDocument();
+
+ const defaultContentTextField = screen.getByTestId('defaultContent-textField');
+ expect(defaultContentTextField).toBeInTheDocument();
+ });
+
+ test('TextFields are visible', () => {
+ render();
+ const branchTextField = screen.getByTestId('branch-textField');
+ expect(branchTextField).toBeVisible();
+
+ const filepathTextField = screen.getByTestId('filepath-textField');
+ expect(filepathTextField).toBeVisible();
+
+ const defaultContentTextField = screen.getByTestId('defaultContent-textField');
+ expect(defaultContentTextField).toBeVisible();
+ });
+
+ test('TextFields handle input change correctly', () => {
+ render();
+ const branchTextField = screen.getByTestId('branch-textField');
+ fireEvent.change(branchTextField, {target: {value: 'branch'}});
+ expect(branchTextField.value).toBe('branch');
+
+ const filepathTextField = screen.getByTestId('filepath-textField');
+ fireEvent.change(filepathTextField, {target: {value: 'filepath'}});
+ expect(filepathTextField.value).toBe('filepath');
+
+ const defaultContentTextField = screen.getByTestId('defaultContent-textField');
+ fireEvent.change(defaultContentTextField, {target: {value: 'defaultContent'}});
+ expect(defaultContentTextField.value).toBe('defaultContent');
+ });
+});
+
+describe('submit button',() => {
+ test('submit button is inside the document', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button).toBeInTheDocument();
+ });
+ test('submit button is visible', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button).toBeVisible();
+ });
+ test('submit button is disabled initially ', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button).toBeDisabled();
+ });
+ test('submit button enabled/disable according to the input value of the branch and the filePath', () => {
+ render();
+ const button = screen.getByTestId('button');
+ const branchTextField = screen.getByTestId('branch-textField');
+ const filepathTextField = screen.getByTestId('filepath-textField');
+ fireEvent.change(filepathTextField, {target: {value: 'filepath'}});
+ fireEvent.change(branchTextField, {target: {value: 'branch'}});
+ expect(button).toBeEnabled();
+ fireEvent.change(filepathTextField, {target: {value: ''}});
+ expect(button).toBeDisabled();
+ fireEvent.change(branchTextField, {target: {value: 'branch'}});
+ fireEvent.change(filepathTextField, {target: {value: ''}});
+ expect(button).toBeDisabled();
+ });
+});
+
+// PropTypes tests
+test('FileForm PropTypes', () => {
+ const conformingProps = {
+ submitText: "text",
+ onSubmit: () => {},
+ branch: 'branch',
+ filepath: 'filepath',
+ defaultContent: 'defaultContent',
+ }
+ checkProps(FileForm, conformingProps)
+});
diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js
new file mode 100644
index 00000000..9948ad6b
--- /dev/null
+++ b/__mocks__/fileMock.js
@@ -0,0 +1,2 @@
+module.exports = 'test-file-stub';
+import '@testing-library/jest-dom'
\ No newline at end of file
diff --git a/__mocks__/testUtils.js b/__mocks__/testUtils.js
new file mode 100644
index 00000000..8f353d68
--- /dev/null
+++ b/__mocks__/testUtils.js
@@ -0,0 +1,12 @@
+import checkPropTypes from 'check-prop-types';
+
+export const findByAttribute = (wrapper, attribute) => wrapper.find(`[data-test='${attribute}']`)
+
+export const checkProps = (component, conformingProps) => {
+ const propError = checkPropTypes(
+ component.propTypes,
+ conformingProps,
+ 'prop',
+ component.name);
+ expect(propError).toBeUndefined();
+}
\ No newline at end of file
diff --git a/jest.enzyme.config.js b/jest.enzyme.config.js
new file mode 100644
index 00000000..e98bc27f
--- /dev/null
+++ b/jest.enzyme.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ 'roots': [
+ '/__mocks__',
+ ],
+ 'moduleNameMapper': {
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js",
+ "\\.(css|less)$": "identity-obj-proxy"
+ },
+ setupFilesAfterEnv: ['/__mocks__/fileMock']
+ };
\ No newline at end of file
diff --git a/package.json b/package.json
index 8ff88f5d..8465aa0f 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"test:e2e": "NODE_ENV=test start-test 6060 cypress:run && nyc report --reporter=json-summary",
"test:unit": "NODE_ENV=test jest ./src/core --coverage && cat ./coverage/lcov.info | coveralls",
"test": "nyc --exclude-after-remap=false npm run test:e2e",
+ "testJestReactTestingLibrary": "jest --watch -c ./jest.enzyme.config.js --verbose",
"create-coverage-badge": "bash scripts/create-badge-json.sh"
},
"browserslist": [
@@ -55,6 +56,10 @@
"@istanbuljs/nyc-config-typescript": "0.1.3",
"@material-ui/core": "^4.7.0",
"@material-ui/icons": "^4.9.1",
+ "@testing-library/dom": "^8.5.0",
+ "@testing-library/jest-dom": "^5.14.1",
+ "@testing-library/react": "^12.1.0",
+ "@testing-library/user-event": "^13.2.1",
"@types/base-64": "0.1.3",
"@types/cypress": "1.1.3",
"@types/jest": "^25.2.1",
@@ -66,14 +71,18 @@
"babel-jest": "24.9.0",
"babel-loader": "^8.0.6",
"babel-plugin-istanbul": "6.0.0",
+ "check-prop-types": "^1.1.2",
"coveralls": "3.0.7",
"cypress": "^6.8.0",
+ "enzyme": "^3.11.0",
+ "enzyme-adapter-react-16": "^1.15.6",
"eslint": "6.6.0",
"eslint-config-prettier": "6.5.0",
"eslint-plugin-chai-friendly": "0.5.0",
"eslint-plugin-cypress": "^2.11.1",
"eslint-plugin-mdx": "1.0.1",
"eslint-plugin-react": "7.19.0",
+ "identity-obj-proxy": "^3.0.0",
"istanbul-lib-coverage": "2.0.5",
"jest": "24.9.0",
"lorem-ipsum": "2.0.1",
diff --git a/src/components/file/FileCard.js b/src/components/file/FileCard.js
index 7e6a965d..78e804b5 100644
--- a/src/components/file/FileCard.js
+++ b/src/components/file/FileCard.js
@@ -22,54 +22,71 @@ const useStyles = makeStyles(theme => ({
}));
function FileCard({
- authentication,
+ isAuthenticated,
repository,
file,
}) {
const classes = useStyles();
const [preview, setPreview] = useState(true);
- const [markdown, setMarkdown] = useState(file ? file.content : '');
+ const [markdown, setMarkdown] = useState('');
const changed = (markdown !== (file && file.content));
const avatarUrl = repository.avatar_url || repository.owner.avatar_url;
const access = repository.permissions.push;
useEffect(() => {
- setMarkdown(file && file.content);
+ setMarkdown(file && file.content ? file.content : '');
}, [file]);
const branch = (file && file.branch) ? file.branch : repository.default_branch;
return (
-
+
}
title={{file && file.path}}
subheader={repository.full_name + '/' + branch}
+ data-testid="cardHeader"
/>
-
+
-
setPreview(!preview)}>
- {!preview ? : }
+ setPreview(!preview)}
+ data-testid="previewButton"
+ >
+ {
+ !preview ?
+ :
+
+ }
{
if (changed) file.save(markdown);
}}
+ data-testid="saveButton"
>
- {changed ? : }
+ {
+ changed ?
+ :
+
+ }
@@ -106,6 +125,11 @@ FileCard.propTypes = {
}),
name: PropTypes.string.isRequired,
avatar_url: PropTypes.string,
+ permissions: PropTypes.shape({
+ push: PropTypes.bool,
+ }).isRequired,
+ full_name: PropTypes.string.isRequired,
+ default_branch: PropTypes.string.isRequired,
}).isRequired,
/** Pass a previously returned file object to bypass the selection. */
file: PropTypes.shape({
@@ -115,13 +139,12 @@ FileCard.propTypes = {
content: PropTypes.string,
branch: PropTypes.string,
filepath: PropTypes.string,
+ save: PropTypes.func.isRequired,
+ dangerouslyDelete: PropTypes.func.isRequired,
+ close: PropTypes.func.isRequired
}),
/** Pass a previously returned authentication object to bypass login. */
- authentication: PropTypes.shape({
- user: PropTypes.object.isRequired,
- token: PropTypes.object.isRequired,
- config: PropTypes.object.isRequired,
- }),
+ isAuthenticated: PropTypes.bool,
};
export default FileCard;
diff --git a/src/components/file/FileForm.js b/src/components/file/FileForm.js
index 040e18c8..a0fbc748 100644
--- a/src/components/file/FileForm.js
+++ b/src/components/file/FileForm.js
@@ -17,28 +17,34 @@ function FileForm({
const disabled = !(filepath);
return (
-
+