diff --git a/example/mockData.ts b/example/mockData.ts
index b79481c..66dba04 100644
--- a/example/mockData.ts
+++ b/example/mockData.ts
@@ -99,6 +99,12 @@ export const legsPushData: Skill[] = [
},
],
},
+ {
+ id: 'something-else',
+ tooltipDescription: 'burn those leg muscles',
+ title: 'Something Else',
+ children: [],
+ },
];
export const legsPullData: Skill[] = [
diff --git a/package.json b/package.json
index 017dcf5..8726f16 100644
--- a/package.json
+++ b/package.json
@@ -62,8 +62,10 @@
"@types/lodash": "^4.14.136",
"@types/react": "^16.8.23",
"@types/react-dom": "^16.8.4",
+ "@types/uuid": "^3.4.5",
"cssnano": "^4.1.10",
"husky": "^3.0.0",
+ "jest-dom": "^3.5.0",
"prettier": "^1.18.2",
"pretty-quick": "^1.11.1",
"react": "^16.8.6",
@@ -75,7 +77,7 @@
"dependencies": {
"@tippy.js/react": "^2.2.2",
"classnames": "^2.2.6",
- "jest-dom": "^3.5.0",
- "lodash": "^4.17.14"
+ "lodash": "^4.17.14",
+ "uuid": "^3.3.2"
}
}
diff --git a/src/__tests__/integeration.test.tsx b/src/__tests__/integeration.test.tsx
index 1fac57d..974e8b5 100644
--- a/src/__tests__/integeration.test.tsx
+++ b/src/__tests__/integeration.test.tsx
@@ -67,6 +67,12 @@ const complexData: SkillType[] = [
},
],
},
+ {
+ id: 'paradigms',
+ title: 'OOP',
+ tooltipDescription: 'for objects',
+ children: [],
+ },
];
function renderComponent(renderComplexTree = false) {
@@ -294,7 +300,7 @@ describe('SkillTreeGroup component', () => {
expect(queryByText('Backend')).toBeTruthy();
expect(getByTestId('selected-count')).toHaveTextContent('0');
- expect(getByTestId('total-count')).toHaveTextContent('9');
+ expect(getByTestId('total-count')).toHaveTextContent('10');
expect(getByTestId('languages')).toHaveClass('Node Node--unlocked');
expect(getByTestId('python')).toHaveClass('Node Node--locked');
diff --git a/src/components/CalculateNodeCount.tsx b/src/components/CalculateNodeCount.tsx
index d284a75..3bc40b4 100644
--- a/src/components/CalculateNodeCount.tsx
+++ b/src/components/CalculateNodeCount.tsx
@@ -1,6 +1,6 @@
import { useContext, useEffect } from 'react';
import { Skill } from 'models';
-import SkillContext from '../context/SkillContext';
+import AppContext from '../context/AppContext';
interface Props {
data: Skill[];
@@ -17,7 +17,7 @@ function calculateNodeCount(data: Skill[]): number {
}
function CalculateNodeCount({ data }: Props) {
- const { addToSkillCount } = useContext(SkillContext);
+ const { addToSkillCount } = useContext(AppContext);
useEffect(() => {
const count = calculateNodeCount(data);
diff --git a/src/components/SkillEdge.tsx b/src/components/SkillEdge.tsx
index 3afad99..6085a2c 100644
--- a/src/components/SkillEdge.tsx
+++ b/src/components/SkillEdge.tsx
@@ -4,27 +4,31 @@ import AngledLine from './ui/AngledLine';
import { NodeState } from 'models';
interface Props {
- position: {
- topX: number;
- topY: number;
- bottomX: number;
- bottomY: number;
- };
+ topX: number;
+ topY: number;
+ bottomX: number;
nodeState: NodeState;
}
-function SkillEdge({ position, nodeState }: Props) {
- if (position.topX === position.bottomX) {
- return ;
+const SkillEdge = React.memo(function({
+ topX,
+ topY,
+ bottomX,
+ nodeState,
+}: Props) {
+ if (topX === bottomX) {
+ return ;
}
return (
);
-}
+});
export default SkillEdge;
diff --git a/src/components/SkillTree.tsx b/src/components/SkillTree.tsx
index 6612919..4c5959d 100644
--- a/src/components/SkillTree.tsx
+++ b/src/components/SkillTree.tsx
@@ -18,7 +18,7 @@ const defaultParentPosition = {
};
function SkillTree({ data, title, treeId }: Props) {
- const [isMobile, setIsMobile] = useState(window.innerWidth < 900);
+ const [isMobile, setIsMobile] = useState(true);
useEffect(() => {
function setState() {
@@ -26,6 +26,7 @@ function SkillTree({ data, title, treeId }: Props) {
}
window.addEventListener('resize', throttle(setState, 250));
+ setState();
return function cleanup() {
window.removeEventListener('resize', throttle(setState, 250));
diff --git a/src/components/SkillTreeSegment.tsx b/src/components/SkillTreeSegment.tsx
index 1bfafbb..8861978 100644
--- a/src/components/SkillTreeSegment.tsx
+++ b/src/components/SkillTreeSegment.tsx
@@ -15,11 +15,10 @@ interface Props {
}
const defaultParentPosition: ChildPosition = {
- top: 0,
center: 0,
};
-const SkillTreeSegment = React.memo(function({
+function SkillTreeSegment({
skill,
parentNodeId,
parentPosition,
@@ -58,17 +57,11 @@ const SkillTreeSegment = React.memo(function({
useEffect(() => {
function calculatePosition() {
- const {
- top,
- left,
- width,
- } = skillNodeRef.current!.getBoundingClientRect();
+ const { left, width } = skillNodeRef.current!.getBoundingClientRect();
- const scrollY = window.scrollY;
const scrollX = window.scrollX;
setChildPosition({
- top: top + scrollY,
center: left + width / 2 + scrollX,
});
}
@@ -90,12 +83,9 @@ const SkillTreeSegment = React.memo(function({
{parentNodeId && (
)}
@@ -103,6 +93,6 @@ const SkillTreeSegment = React.memo(function({
);
-});
+}
export default SkillTreeSegment;
diff --git a/src/components/__tests__/CalculateNodeCount.test.tsx b/src/components/__tests__/CalculateNodeCount.test.tsx
index 0a163b3..62c2c92 100644
--- a/src/components/__tests__/CalculateNodeCount.test.tsx
+++ b/src/components/__tests__/CalculateNodeCount.test.tsx
@@ -1,13 +1,9 @@
import React, { useContext } from 'react';
import { render } from '@testing-library/react';
import CalculateSkillNodes from '../CalculateNodeCount';
-import { Skill } from 'models';
+import { Skill } from '../../models';
import AppContext from '../../context/AppContext';
import { SkillTreeProvider } from '../../context/SkillContext';
-import {
- legsPullData,
- legsPushData,
-} from '../../components/__mocks__/mockData';
interface GetDummyCounterProps {
children: (skillCount: number) => JSX.Element;
@@ -51,17 +47,4 @@ describe('CalculateSkillNodes component', () => {
expect(getCounter).toBe(0);
});
-
- it('should correctly calculate a single branch tree', async () => {
- const { getCounter, debug } = renderComponent(legsPullData);
- debug();
-
- expect(getCounter).toBe(6);
- });
-
- it('should correctly calculate a tree with multiples branches', () => {
- const { getCounter } = renderComponent(legsPushData);
-
- expect(getCounter).toBe(6);
- });
});
diff --git a/src/components/__tests__/SkillEdge.test.tsx b/src/components/__tests__/SkillEdge.test.tsx
index 75df485..aac1ec3 100644
--- a/src/components/__tests__/SkillEdge.test.tsx
+++ b/src/components/__tests__/SkillEdge.test.tsx
@@ -7,13 +7,15 @@ const defaultPosition = {
topX: 0,
topY: 0,
bottomX: 0,
- bottomY: 0,
};
function renderComponent(startingState: NodeState, position = defaultPosition) {
let state = startingState;
+ const { topX, topY, bottomX } = position;
- return render();
+ return render(
+
+ );
}
describe('SkillEdge', () => {
@@ -56,14 +58,12 @@ describe('SkillEdge', () => {
topX: 100,
topY: 100,
bottomX: 50,
- bottomY: 150,
};
const rightAngledLinePosition = {
topX: 100,
topY: 100,
bottomX: 150,
- bottomY: 150,
};
it('should be inactive if the next node is unlocked', async () => {
diff --git a/src/components/__tests__/SkillNode.test.tsx b/src/components/__tests__/SkillNode.test.tsx
index da58893..bbba2a1 100644
--- a/src/components/__tests__/SkillNode.test.tsx
+++ b/src/components/__tests__/SkillNode.test.tsx
@@ -1,14 +1,8 @@
import React from 'react';
-import { render, act, fireEvent } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import SkillNode from '../SkillNode';
import { NodeState } from 'models';
-function fireResize(width: number) {
- // @ts-ignore
- window.innerWidth = width;
- window.dispatchEvent(new Event('resize'));
-}
-
function renderComponent(nodeState: NodeState = 'locked') {
return render(
{
});
it('should handle resizing of the window correctly', () => {
- const resizeEvent = document.createEvent('Event');
- resizeEvent.initEvent('resize', true, true);
+ // @ts-ignore
+ window.innerWidth = 200;
renderComponent();
- // empty until i can work out how to attach the renderedComponnet to the DOM
- // otherwise getBoundingClientRect() always returns 0.
- act(() => {
- fireResize(400);
- });
+ // check that hr exists
});
});
diff --git a/src/components/__tests__/SkillTree.test.tsx b/src/components/__tests__/SkillTree.test.tsx
index 2dbe608..99b8485 100644
--- a/src/components/__tests__/SkillTree.test.tsx
+++ b/src/components/__tests__/SkillTree.test.tsx
@@ -4,7 +4,6 @@ import SkillTree from '../SkillTree';
import MockLocalStorage from '../__mocks__/mockLocalStorage';
import { SkillProvider } from '../../context/AppContext';
import SkillTreeGroup from '../../components/SkillTreeGroup';
-import { SkillTreeProvider } from '../../context/SkillContext';
const mockSkillTreeData = [
{
@@ -42,11 +41,9 @@ const mockSkillTreeData = [
];
const defaultStoreContents = {
- [`skills-test`]: JSON.stringify({}),
+ [`skills-bl`]: JSON.stringify({}),
};
-const storage = new MockLocalStorage(defaultStoreContents);
-
function renderComponent() {
let selectedSkillCount: number;
let resetSkills: VoidFunction;
@@ -58,13 +55,11 @@ function renderComponent() {
selectedSkillCount = treeData.selectedSkillCount;
resetSkills = treeData.resetSkills;
return (
-
-
-
+
);
}}
@@ -82,14 +77,13 @@ function renderComponent() {
};
}
-function fireResize(width: number) {
- // @ts-ignore
- window.innerWidth = width;
- window.dispatchEvent(new Event('resize'));
-}
-
afterEach(() => {
- storage.setItem('skills-test', JSON.stringify({}));
+ window.localStorage.setItem('skills-bl', JSON.stringify({}));
+});
+
+beforeEach(() => {
+ //@ts-ignore
+ window.localStorage = new MockLocalStorage(defaultStoreContents);
});
describe('SkillTree', () => {
@@ -168,7 +162,7 @@ describe('SkillTree', () => {
'item-three': 'locked',
};
- storage.setItem(`skills-test`, JSON.stringify(defaultSkills));
+ window.localStorage.setItem(`skills-bl`, JSON.stringify(defaultSkills));
const { getByTestId, getSelectedSkillCount } = renderComponent();
@@ -183,7 +177,7 @@ describe('SkillTree', () => {
expect(getSelectedSkillCount()).toBe(1);
});
- xit('should deselect all skill trees when resetSkills is invoked', () => {
+ it('should deselect all skill trees when resetSkills is invoked', () => {
const {
getByTestId,
getSelectedSkillCount,
@@ -206,7 +200,7 @@ describe('SkillTree', () => {
resetSkillsHandler();
- expect(topNode).toHaveClass('Node Node--unlocked');
+ expect(topNode).toHaveClass('Node Node--locked');
expect(middleNode).toHaveClass('Node Node--locked');
expect(bottomNode).toHaveClass('Node Node--locked');
@@ -222,21 +216,29 @@ describe('SkillTree', () => {
expect(queryByTestId('h-separator')).toBeTruthy();
});
- xit('should correctly handle resizing from desktop to mobile', () => {
- const resizeEvent = document.createEvent('Event');
+ xdescribe('resizing', () => {
+ function fireResize(width: number) {
+ // @ts-ignore
+ window.innerWidth = width;
+ window.dispatchEvent(new Event('resize'));
+ }
- // @ts-ignore
- window.innerWidth = 1000;
- resizeEvent.initEvent('resize', false, false);
+ it('should handle resizing from desktop to mobile', () => {
+ const resizeEvent = document.createEvent('Event');
- const { queryByTestId } = renderComponent();
+ // @ts-ignore
+ window.innerWidth = 1000;
+ resizeEvent.initEvent('resize', true, true);
- expect(queryByTestId('h-separator')).toBeFalsy();
+ const { queryByTestId } = renderComponent();
- act(() => {
- fireResize(400);
- });
+ expect(queryByTestId('h-separator')).toBeFalsy();
- expect(queryByTestId('h-separator')).toBeTruthy();
+ act(() => {
+ fireResize(400);
+ });
+
+ expect(queryByTestId('h-separator')).toBeTruthy();
+ });
});
});
diff --git a/src/components/ui/Icon.tsx b/src/components/ui/Icon.tsx
index e008885..805ccda 100644
--- a/src/components/ui/Icon.tsx
+++ b/src/components/ui/Icon.tsx
@@ -6,7 +6,7 @@ export interface Props {
title: string;
}
-function Icon({ src, title, containerWidth }: Props) {
+const Icon = React.memo(function({ src, title, containerWidth }: Props) {
return (
);
-}
+});
export default Icon;
diff --git a/src/components/ui/TooltipContent.tsx b/src/components/ui/TooltipContent.tsx
index fbcc24f..f159fda 100644
--- a/src/components/ui/TooltipContent.tsx
+++ b/src/components/ui/TooltipContent.tsx
@@ -5,13 +5,16 @@ type Props = {
title: string;
};
-function TooltipContent({ tooltipDescription, title }: Props) {
+const TooltipContent = React.memo(function({
+ tooltipDescription,
+ title,
+}: Props) {
return (
{title}
{tooltipDescription}
);
-}
+});
export default TooltipContent;
diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx
index df690ac..e22cc0e 100644
--- a/src/context/AppContext.tsx
+++ b/src/context/AppContext.tsx
@@ -1,20 +1,24 @@
import * as React from 'react';
+import uuid from 'uuid';
interface State {
+ resetId: string;
skillCount: number;
selectedSkillCount: number;
}
export interface IAppContext {
+ resetId: string;
skillCount: number;
selectedSkillCount: number;
- incrementSelectedSkillCount: (number: number) => void;
decrementSelectedSkillCount: VoidFunction;
- addToSkillCount: (number: number) => void;
resetSkills: VoidFunction;
+ incrementSelectedSkillCount: (number: number) => void;
+ addToSkillCount: (number: number) => void;
}
const AppContext = React.createContext({
+ resetId: '',
skillCount: 0,
selectedSkillCount: 0,
incrementSelectedSkillCount: () => undefined,
@@ -25,8 +29,9 @@ const AppContext = React.createContext({
export class SkillProvider extends React.Component<{}, State> {
state = {
- selectedSkillCount: 0,
+ resetId: '',
skillCount: 0,
+ selectedSkillCount: 0,
};
// refactor the increment items to use just one method.
@@ -53,21 +58,10 @@ export class SkillProvider extends React.Component<{}, State> {
};
resetSkills = () => {
- // return this.setState(prevState => {
- // const { globalSkills } = prevState;
- // let skillTrees: Dictionary = {};
- // Object.keys(globalSkills).map(key => {
- // const resettedValues = mapValues(
- // globalSkills[key],
- // (): NodeState => LOCKED_STATE
- // );
- // skillTrees[key] = resettedValues;
- // });
- // return {
- // globalSkills: skillTrees,
- // selectedSkillCount: 0,
- // };
- // });
+ this.setState({
+ resetId: uuid(),
+ selectedSkillCount: 0,
+ });
};
render() {
@@ -80,6 +74,7 @@ export class SkillProvider extends React.Component<{}, State> {
decrementSelectedSkillCount: this.decrementSelectedSkillCount,
selectedSkillCount: this.state.selectedSkillCount,
resetSkills: this.resetSkills,
+ resetId: this.state.resetId,
}}
>
{this.props.children}
diff --git a/src/context/SkillContext.tsx b/src/context/SkillContext.tsx
index 09a6beb..5520d0b 100644
--- a/src/context/SkillContext.tsx
+++ b/src/context/SkillContext.tsx
@@ -1,8 +1,9 @@
import React from 'react';
+import { mapValues } from 'lodash';
import { NodeState, ContextStorage } from '../models';
import { Dictionary } from '../models/utils';
import AppContext, { IAppContext } from './AppContext';
-import { SELECTED_STATE } from '../components/constants';
+import { SELECTED_STATE, LOCKED_STATE } from '../components/constants';
type Props = typeof SkillTreeProvider.defaultProps & {
treeId: string;
@@ -16,6 +17,7 @@ type Skills = Dictionary;
interface State {
skills: Skills;
+ resetId: string;
}
export interface ISkillContext {
@@ -23,7 +25,6 @@ export interface ISkillContext {
updateSkillState: (key: string, updatedState: NodeState) => void;
incrementSelectedSkillCount: VoidFunction;
decrementSelectedSkillCount: VoidFunction;
- addToSkillCount: (count: number) => void;
}
const SkillContext = React.createContext({
@@ -31,7 +32,6 @@ const SkillContext = React.createContext({
updateSkillState: () => undefined,
incrementSelectedSkillCount: () => undefined,
decrementSelectedSkillCount: () => undefined,
- addToSkillCount: () => undefined,
});
export class SkillTreeProvider extends React.Component {
@@ -58,6 +58,7 @@ export class SkillTreeProvider extends React.Component {
this.state = {
skills: treeSkills,
+ resetId: context.resetId,
};
}
@@ -69,8 +70,23 @@ export class SkillTreeProvider extends React.Component {
window.removeEventListener('beforeunload', this.writeToStorage);
}
- addToSkillCount = (count: number) => {
- return this.context.addToSkillCount(count);
+ componentDidUpdate() {
+ if (this.context.resetId !== this.state.resetId) {
+ this.resetSkills();
+ }
+ }
+
+ resetSkills = () => {
+ return this.setState(prevState => {
+ const { skills } = prevState;
+
+ const resettedSkills = mapValues(skills, (): NodeState => LOCKED_STATE);
+
+ return {
+ skills: resettedSkills,
+ resetId: this.context.resetId,
+ };
+ });
};
updateSkillState = (key: string, updatedState: NodeState) => {
@@ -101,7 +117,6 @@ export class SkillTreeProvider extends React.Component {
updateSkillState: this.updateSkillState,
incrementSelectedSkillCount: this.context.incrementSelectedSkillCount,
decrementSelectedSkillCount: this.context.decrementSelectedSkillCount,
- addToSkillCount: this.addToSkillCount,
}}
>
{this.props.children}
diff --git a/src/models/index.ts b/src/models/index.ts
index 4d43001..6e5a172 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -25,7 +25,6 @@ export type ParentPosition = {
};
export type ChildPosition = {
- top: number;
center: number;
};