actions', () => {
76 | it('toggles MODE to LIGHT', () => {
77 | const store = mockStore({
78 | js: 'es5',
79 | mode: 'dark'
80 | });
81 |
82 | const toggle = mount(
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | );
91 | toggle.find('button').simulate('click');
92 |
93 | const actions = store.getActions();
94 | expect(actions).toMatchObject([{ type: TOGGLE, payload: 'mode' }]);
95 | });
96 |
97 | it('toggles MODE to DARK', () => {
98 | const store = mockStore({
99 | js: 'es5',
100 | mode: 'light'
101 | });
102 |
103 | const toggle = mount(
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | );
112 | toggle.find('button').simulate('click');
113 |
114 | const actions = store.getActions();
115 | expect(actions).toMatchObject([{ type: TOGGLE, payload: 'mode' }]);
116 | });
117 |
118 | it('toggles JS to ES6', () => {
119 | const store = mockStore({
120 | js: 'es5',
121 | mode: 'light'
122 | });
123 |
124 | const toggle = mount(
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | );
133 | toggle.find('button').simulate('click');
134 |
135 | const actions = store.getActions();
136 | expect(actions).toMatchObject([{ type: TOGGLE, payload: 'js' }]);
137 | });
138 |
139 | it('toggles JS to ES5', () => {
140 | const store = mockStore({
141 | js: 'es6',
142 | mode: 'light'
143 | });
144 |
145 | const toggle = mount(
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | );
154 | toggle.find('button').simulate('click');
155 |
156 | const actions = store.getActions();
157 | expect(actions).toMatchObject([{ type: TOGGLE, payload: 'js' }]);
158 | });
159 | });
160 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/Button.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component renders with a DARK theme 1`] = `
4 | .c0 {
5 | background-color: #484848;
6 | border: 1px solid #484848;
7 | border-radius: 4px;
8 | cursor: pointer;
9 | font: 400 1rem 'Karla','sans-serif';
10 | height: 48px;
11 | max-width: 240px;
12 | outline: none;
13 | padding: 0 1.5rem;
14 | width: 37.5%;
15 | }
16 |
17 | .c0 span {
18 | color: #F5F5F5;
19 | }
20 |
21 | .c0:hover {
22 | background-color: #888888;
23 | border-color: #888888;
24 | }
25 |
26 | .c0:hover span {
27 | color: #FFFFFF;
28 | }
29 |
30 |
35 |
36 | Test Button
37 |
38 |
39 | `;
40 |
41 | exports[` component renders with a LIGHT theme 1`] = `
42 | .c0 {
43 | background-color: #F2E8F2;
44 | border: 1px solid #EDB8ED;
45 | border-radius: 4px;
46 | cursor: pointer;
47 | font: 400 1rem 'Karla','sans-serif';
48 | height: 48px;
49 | max-width: 240px;
50 | outline: none;
51 | padding: 0 1.5rem;
52 | width: 37.5%;
53 | }
54 |
55 | .c0 span {
56 | color: #6F256F;
57 | }
58 |
59 | .c0:hover {
60 | background-color: #6F256F;
61 | border-color: #6F256F;
62 | }
63 |
64 | .c0:hover span {
65 | color: #FFFFFF;
66 | }
67 |
68 |
73 |
74 | Test Button
75 |
76 |
77 | `;
78 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/Code.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`
component renders ES5 code in LIGHT mode 1`] = `
4 |
17 |
18 | JavaScript ES5
19 |
20 |
21 | `;
22 |
23 | exports[`
component renders ES6 code in DARK mode 1`] = `
24 |
37 |
38 | JavaScript ES6
39 |
40 |
41 | `;
42 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/CodePreTag.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component renders children properly 1`] = `
4 |
5 |
6 |
7 | function foo() {
8 | return 'bar';
9 | }
10 |
11 |
12 |
13 | `;
14 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/PatternsList.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`REFERENCE - Patterns List renders all the patterns 1`] = `
4 | .c0 a {
5 | border-bottom: 1px solid transparent;
6 | -webkit-text-decoration: none;
7 | text-decoration: none;
8 | }
9 |
10 | .c0 a:hover {
11 | border-bottom: 1px solid;
12 | }
13 |
14 | .c0 h2,
15 | .c0 h3 {
16 | margin-top: 2.5rem;
17 | }
18 |
19 | .c0 h3 {
20 | border-bottom: 1px solid;
21 | padding-bottom: 1rem;
22 | }
23 |
24 |
27 |
28 | Design Patterns
29 |
30 |
31 | In software engineering, a design pattern is a general repeatable solution to a commonly occurring problem in software design.
32 |
33 |
34 | Creational Design Patterns
35 |
36 |
37 | These design patterns are all about class instantiation. This pattern can be further divided into class-creation patterns and object-creational patterns. While class-creation patterns use inheritance effectively in the instantiation process, object-creation patterns use delegation effectively to get the job done.
38 |
39 |
81 |
82 | Structural Design Patterns
83 |
84 |
85 | These design patterns are all about Class and Object composition. Structural class-creation patterns use inheritance to compose interfaces. Structural object-patterns define ways to compose objects to obtain new functionality.
86 |
87 |
145 |
146 | Behavioral Design Patterns
147 |
148 |
149 | These design patterns are all about Class's objects communication. Behavioral patterns are those patterns that are most specifically concerned with communication between objects.
150 |
151 |
241 |
242 | `;
243 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/Percentage.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component matches the snapshot 1`] = `
4 | .c0 {
5 | border: 4px solid red;
6 | border-radius: 50%;
7 | color: red;
8 | font-family: 'Karla','sans-serif';
9 | font-size: 4rem;
10 | height: 10rem;
11 | margin: auto;
12 | line-height: 10rem;
13 | text-align: center;
14 | width: 10rem;
15 | }
16 |
17 |
18 |
21 | 5
22 | %
23 |
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/ProgressBar.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component matches the snapshot 1`] = `
4 | .c0 {
5 | display: -webkit-box;
6 | display: -webkit-flex;
7 | display: -ms-flexbox;
8 | display: flex;
9 | height: 3px;
10 | -webkit-box-pack: justify;
11 | -webkit-justify-content: space-between;
12 | -ms-flex-pack: justify;
13 | justify-content: space-between;
14 | margin: 1.25rem 0 1rem;
15 | width: 100%;
16 | }
17 |
18 | .c1 {
19 | background: #9ACD32;
20 | display: -webkit-box;
21 | display: -webkit-flex;
22 | display: -ms-flexbox;
23 | display: flex;
24 | height: 2px;
25 | width: 3.75%;
26 | }
27 |
28 | .c2 {
29 | background: #E22A23;
30 | display: -webkit-box;
31 | display: -webkit-flex;
32 | display: -ms-flexbox;
33 | display: flex;
34 | height: 2px;
35 | width: 3.75%;
36 | }
37 |
38 | .c3 {
39 | background: #D8D8D8;
40 | display: -webkit-box;
41 | display: -webkit-flex;
42 | display: -ms-flexbox;
43 | display: flex;
44 | height: 2px;
45 | width: 3.75%;
46 | }
47 |
48 |
49 |
52 |
55 |
58 |
61 |
64 |
67 |
70 |
73 |
76 |
77 |
78 | `;
79 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/Result.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component matches the snapshot 1`] = `
4 | .c0 {
5 | margin: 3rem 0;
6 | text-align: center;
7 | }
8 |
9 |
10 |
13 | You got
14 |
15 | 3
16 |
17 | patterns right out of
18 | 4
19 | .
20 |
21 |
22 | `;
23 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/SVG.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component renders JS control 1`] = `
4 |
5 |
12 |
15 |
18 |
21 |
24 |
25 |
26 |
27 | `;
28 |
29 | exports[` component renders MODE control in DARK mode 1`] = `
30 |
47 | `;
48 |
49 | exports[` component renders MODE control in LIGHT mode 1`] = `
50 |
67 | `;
68 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/Title.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` component renders with a DARK theme 1`] = `
4 | .c0 {
5 | padding: 0.75rem 0 1rem;
6 | border-radius: 4px;
7 | background: #282828;
8 | margin: 1rem 0 0;
9 | text-align: center;
10 | width: 100%;
11 | }
12 |
13 | .c1 {
14 | font-family: 'Karla',sans-serif;
15 | font-size: 1.75rem;
16 | color: #E22A23;
17 | margin: 0;
18 | }
19 |
20 | .c2 {
21 | font: 400 0.875rem 'Karla',sans-serif;
22 | color: #C8C8C8;
23 | margin: 0.75rem 0 0;
24 | }
25 |
26 |
29 |
32 | Design Patterns Game
33 |
34 |
37 | “Gang of Four” patterns in JavaScript
38 |
39 |
40 | `;
41 |
42 | exports[` component renders with a LIGHT theme 1`] = `
43 | .c0 {
44 | padding: 0.75rem 0 1rem;
45 | border-radius: 4px;
46 | background: #F2E8F2;
47 | margin: 1rem 0 0;
48 | text-align: center;
49 | width: 100%;
50 | }
51 |
52 | .c1 {
53 | font-family: 'Karla',sans-serif;
54 | font-size: 1.75rem;
55 | color: #6F256F;
56 | margin: 0;
57 | }
58 |
59 | .c2 {
60 | font: 400 0.875rem 'Karla',sans-serif;
61 | color: #6F256F;
62 | margin: 0.75rem 0 0;
63 | }
64 |
65 |
68 |
71 | Design Patterns Game
72 |
73 |
76 | “Gang of Four” patterns in JavaScript
77 |
78 |
79 | `;
80 |
--------------------------------------------------------------------------------
/__tests__/components/__snapshots__/Toggle.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` renders JS toggle in DARK more 1`] = `
4 | .c0 {
5 | background-color: #F2E8F2;
6 | border: 1px solid #EDB8ED;
7 | border-radius: 50%;
8 | cursor: pointer;
9 | height: 2.5rem;
10 | margin: 0.5rem 1.5rem 0 0;
11 | outline: 0;
12 | width: 2.5rem;
13 | z-index: 10;
14 | }
15 |
16 | .c0 svg,
17 | .c0 g {
18 | fill: #A568A5;
19 | }
20 |
21 | .c0:hover {
22 | background-color: #EDB8ED;
23 | }
24 |
25 | .c0:hover svg,
26 | .c0:hover g {
27 | fill: #6F256F;
28 | }
29 |
30 | .c0.active {
31 | background-color: #A568A5;
32 | border-color: #6F256F;
33 | }
34 |
35 | .c0.active svg,
36 | .c0.active g {
37 | fill: #F2E8F2;
38 | }
39 |
40 | @media (min-width:769px) {
41 | .c0 {
42 | margin: 0 0 0 1.5rem;
43 | }
44 | }
45 |
46 |
47 |
52 |
59 |
62 |
65 |
68 |
71 |
72 |
73 |
74 |
75 | `;
76 |
77 | exports[` renders JS toggle in LIGHT more 1`] = `
78 | .c0 {
79 | background-color: #F2E8F2;
80 | border: 1px solid #EDB8ED;
81 | border-radius: 50%;
82 | cursor: pointer;
83 | height: 2.5rem;
84 | margin: 0.5rem 1.5rem 0 0;
85 | outline: 0;
86 | width: 2.5rem;
87 | z-index: 10;
88 | }
89 |
90 | .c0 svg,
91 | .c0 g {
92 | fill: #A568A5;
93 | }
94 |
95 | .c0:hover {
96 | background-color: #EDB8ED;
97 | }
98 |
99 | .c0:hover svg,
100 | .c0:hover g {
101 | fill: #6F256F;
102 | }
103 |
104 | .c0.active {
105 | background-color: #A568A5;
106 | border-color: #6F256F;
107 | }
108 |
109 | .c0.active svg,
110 | .c0.active g {
111 | fill: #F2E8F2;
112 | }
113 |
114 | @media (min-width:769px) {
115 | .c0 {
116 | margin: 0 0 0 1.5rem;
117 | }
118 | }
119 |
120 |
121 |
126 |
133 |
136 |
139 |
142 |
145 |
146 |
147 |
148 |
149 | `;
150 |
151 | exports[` renders the MODE toggle in DARK mode 1`] = `
152 | .c0 {
153 | background-color: #F2E8F2;
154 | border: 1px solid #EDB8ED;
155 | border-radius: 50%;
156 | cursor: pointer;
157 | height: 2.5rem;
158 | margin: 0.5rem 1.5rem 0 0;
159 | outline: 0;
160 | width: 2.5rem;
161 | z-index: 10;
162 | }
163 |
164 | .c0 svg,
165 | .c0 g {
166 | fill: #A568A5;
167 | }
168 |
169 | .c0:hover {
170 | background-color: #EDB8ED;
171 | }
172 |
173 | .c0:hover svg,
174 | .c0:hover g {
175 | fill: #6F256F;
176 | }
177 |
178 | .c0.active {
179 | background-color: #A568A5;
180 | border-color: #6F256F;
181 | }
182 |
183 | .c0.active svg,
184 | .c0.active g {
185 | fill: #F2E8F2;
186 | }
187 |
188 | @media (min-width:769px) {
189 | .c0 {
190 | margin: 0 0 0 1.5rem;
191 | }
192 | }
193 |
194 |
195 |
200 |
207 |
211 |
214 |
215 |
216 |
217 | `;
218 |
219 | exports[` renders the MODE toggle in LIGHT mode 1`] = `
220 | .c0 {
221 | background-color: #F2E8F2;
222 | border: 1px solid #EDB8ED;
223 | border-radius: 50%;
224 | cursor: pointer;
225 | height: 2.5rem;
226 | margin: 0.5rem 1.5rem 0 0;
227 | outline: 0;
228 | width: 2.5rem;
229 | z-index: 10;
230 | }
231 |
232 | .c0 svg,
233 | .c0 g {
234 | fill: #A568A5;
235 | }
236 |
237 | .c0:hover {
238 | background-color: #EDB8ED;
239 | }
240 |
241 | .c0:hover svg,
242 | .c0:hover g {
243 | fill: #6F256F;
244 | }
245 |
246 | .c0.active {
247 | background-color: #A568A5;
248 | border-color: #6F256F;
249 | }
250 |
251 | .c0.active svg,
252 | .c0.active g {
253 | fill: #F2E8F2;
254 | }
255 |
256 | @media (min-width:769px) {
257 | .c0 {
258 | margin: 0 0 0 1.5rem;
259 | }
260 | }
261 |
262 |
263 |
268 |
275 |
279 |
282 |
283 |
284 |
285 | `;
286 |
--------------------------------------------------------------------------------
/__tests__/config/configure-enzyme.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/__tests__/config/register-context.js:
--------------------------------------------------------------------------------
1 | import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
2 |
3 | registerRequireContextHook();
4 |
--------------------------------------------------------------------------------
/__tests__/helpers/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import { ThemeProvider } from 'styled-components';
4 | import { themeLight } from '../../src/styles/themes/theme.light';
5 | import { themeDark } from '../../src/styles/themes/theme.dark';
6 |
7 | export const renderWithLightTheme = component =>
8 | renderer.create({component} );
9 |
10 | export const renderWithDarkTheme = component =>
11 | renderer.create({component} );
12 |
--------------------------------------------------------------------------------
/__tests__/localStorage.js:
--------------------------------------------------------------------------------
1 | import {
2 | loadState,
3 | saveState
4 | } from '../src/helpers/localStorage';
5 |
6 | describe('LocalStorage utilities', () => {
7 | beforeEach(() => {
8 | window.localStorage.clear();
9 | });
10 |
11 | const data = {
12 | foo: 1
13 | };
14 |
15 | it('should save and load state', () => {
16 | saveState(data);
17 |
18 | const state = loadState();
19 | expect(state).toMatchObject(data);
20 | });
21 |
22 | it('should not throw exceptions if localStorage is undefined', () => {
23 | Reflect.deleteProperty(window, 'localStorage');
24 |
25 | expect(saveState(data)).toBeUndefined();
26 | expect(loadState()).toBeUndefined();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/__tests__/middleware/submit.test.js:
--------------------------------------------------------------------------------
1 | import configureMockStore from 'redux-mock-store';
2 | import { submitMiddleware } from '../../src/middleware/submit';
3 | import { SUBMIT } from '../../src/static/constants/actions';
4 |
5 | const next = jest.fn();
6 | const mockStore = configureMockStore();
7 | const patterns = [
8 | {
9 | uuid: 'abc123',
10 | name: 'Prototype',
11 | type: 'creational',
12 | codeES5: 'Code ES5',
13 | codeES6: 'Code ES6',
14 | answered: false,
15 | answerId: null,
16 | correct: null
17 | },
18 |
19 | {
20 | uuid: 'abc234',
21 | name: 'Singleton',
22 | type: 'creational',
23 | codeES5: 'Code ES5',
24 | codeES6: 'Code ES6',
25 | answered: false,
26 | answerId: null,
27 | correct: null
28 | }
29 | ];
30 |
31 | describe('Submit middleware', () => {
32 | it('should update the payload upon SUBMIT', () => {
33 | const store = mockStore({
34 | progress: {
35 | answers: [],
36 | remaining: patterns,
37 | current: patterns[0]
38 | }
39 | });
40 | const action = { type: SUBMIT, payload: 'abc123' };
41 |
42 | submitMiddleware(store)(next)(action);
43 |
44 | expect(next).toHaveBeenCalledWith({
45 | payload: {
46 | currentIndex: 0,
47 | recentlyAnswered: {
48 | answerId: 'abc123',
49 | answered: true,
50 | correct: true,
51 | name: 'Prototype',
52 | type: 'creational',
53 | uuid: 'abc123'
54 | },
55 | remainingPatterns: [
56 | {
57 | answerId: null,
58 | answered: false,
59 | codeES5: 'Code ES5',
60 | codeES6: 'Code ES6',
61 | correct: null,
62 | name: 'Singleton',
63 | type: 'creational',
64 | uuid: 'abc234'
65 | }
66 | ]
67 | },
68 | type: SUBMIT
69 | });
70 | });
71 |
72 | it('should return _next_ when the action is not SUBMIT', () => {
73 | const store = mockStore({});
74 | const action = { type: 'ERROR' };
75 |
76 | submitMiddleware(store)(next)(action);
77 |
78 | expect(store.getActions()[0]).toBeFalsy();
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/__tests__/middleware/toggle.test.js:
--------------------------------------------------------------------------------
1 | import configureMockStore from 'redux-mock-store';
2 | import { toggleMiddleware } from '../../src/middleware/toggle';
3 | import { TOGGLE, TOGGLE_JS, TOGGLE_MODE } from '../../src/static/constants/actions';
4 |
5 | const next = jest.fn();
6 | const mockStore = configureMockStore();
7 |
8 | describe('Toggle middleware', () => {
9 | it('should dispatch TOGGLE_JS when the payload is `js`', () => {
10 | const mockDispatch = jest.fn();
11 |
12 | const store = mockStore({
13 | js: 'es5',
14 | dispatch: mockDispatch
15 | });
16 | const action = { type: TOGGLE, payload: 'js' };
17 |
18 | toggleMiddleware(store)(next)(action);
19 |
20 | expect(store.getActions()[0]).toEqual({
21 | type: TOGGLE_JS,
22 | payload: 'es6'
23 | });
24 |
25 | expect(next).toHaveBeenCalled();
26 | });
27 |
28 | it('should dispatch TOGGLE_JS when the payload is `js`', () => {
29 | const mockDispatch = jest.fn();
30 |
31 | const store = mockStore({
32 | js: 'es6',
33 | dispatch: mockDispatch
34 | });
35 | const action = { type: TOGGLE, payload: 'js' };
36 |
37 | toggleMiddleware(store)(next)(action);
38 |
39 | expect(store.getActions()[0]).toEqual({
40 | type: TOGGLE_JS,
41 | payload: 'es5'
42 | });
43 |
44 | expect(next).toHaveBeenCalled();
45 | });
46 |
47 | it('should dispatch TOGGLE_MODE when the payload is `mode`', () => {
48 | const mockDispatch = jest.fn();
49 |
50 | const store = mockStore({
51 | mode: 'dark',
52 | dispatch: mockDispatch
53 | });
54 | const action = { type: TOGGLE, payload: 'mode' };
55 |
56 | toggleMiddleware(store)(next)(action);
57 |
58 | expect(store.getActions()[0]).toEqual({
59 | type: TOGGLE_MODE,
60 | payload: 'light'
61 | });
62 | });
63 |
64 | it('should dispatch TOGGLE_MODE when the payload is `mode`', () => {
65 | const mockDispatch = jest.fn();
66 |
67 | const store = mockStore({
68 | mode: 'light',
69 | dispatch: mockDispatch
70 | });
71 | const action = { type: TOGGLE, payload: 'mode' };
72 |
73 | toggleMiddleware(store)(next)(action);
74 |
75 | expect(store.getActions()[0]).toEqual({
76 | type: TOGGLE_MODE,
77 | payload: 'dark'
78 | });
79 | });
80 |
81 | it('should return _next_ when the action is not TOGGLE', () => {
82 | const store = mockStore({});
83 | const action = { type: 'ERROR' };
84 |
85 | toggleMiddleware(store)(next)(action);
86 |
87 | expect(store.getActions()[0]).toBeFalsy();
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/__tests__/pages/About.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import 'jest-styled-components';
4 | import { ThemeProvider } from 'styled-components';
5 | import { themeLight } from '../../src/styles/themes/theme.light';
6 | import { themeDark } from '../../src/styles/themes/theme.dark';
7 | import About from '../../src/pages/About';
8 |
9 | describe('About page', () => {
10 | it('renders with a LIGHT theme', () => {
11 | const tree = renderer
12 | .create(
13 |
14 |
15 |
16 | )
17 | .toJSON();
18 | expect(tree).toMatchSnapshot();
19 | });
20 |
21 | it('renders with a DARK theme', () => {
22 | const tree = renderer
23 | .create(
24 |
25 |
26 |
27 | )
28 | .toJSON();
29 | expect(tree).toMatchSnapshot();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/__tests__/pages/Game.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import 'jest-styled-components';
3 | import { mount } from 'enzyme';
4 | import { Provider } from 'react-redux';
5 | import configureMockStore from 'redux-mock-store';
6 | import { ThemeProvider } from 'styled-components';
7 | import { themeLight } from '../../src/styles/themes/theme.light';
8 | import { themeDark } from '../../src/styles/themes/theme.dark';
9 | import styleLight from '../../src/styles/hljs/hljs.light';
10 | import styleDark from '../../src/styles/hljs/hljs.dark';
11 | import Game from '../../src/pages/Game';
12 | import { START, RESTART } from '../../src/static/constants/actions';
13 |
14 | const patterns = [
15 | {
16 | uuid: 'abc123',
17 | name: 'Prototype',
18 | type: 'creational',
19 | codeES5: 'Code ES5 - Prototype',
20 | codeES6: 'Code ES6 - Prototype',
21 | answered: false,
22 | answerId: null,
23 | correct: null
24 | },
25 |
26 | {
27 | uuid: 'abc234',
28 | name: 'SIngleton',
29 | type: 'creational',
30 | codeES5: 'Code ES5 - Singleton',
31 | codeES6: 'Code ES6 - Singleton',
32 | answered: false,
33 | answerId: null,
34 | correct: null
35 | }
36 | ];
37 |
38 | const mockStore = configureMockStore();
39 | const store = mockStore({
40 | patterns: patterns,
41 | progress: {
42 | answers: [],
43 | remaining: [patterns[1]],
44 | current: patterns[0]
45 | },
46 | intro: false,
47 | mode: 'light',
48 | js: 'es5'
49 | });
50 |
51 | describe('Game page - GAME - LIGHT style', () => {
52 | let tree;
53 |
54 | beforeEach(() => {
55 | tree = mount(
56 |
57 |
58 |
59 |
60 |
61 | );
62 | });
63 |
64 | it('renders a component', () => {
65 | expect(tree.find('ProgressBar')).toMatchSnapshot();
66 | });
67 |
68 | it('renders a
component', () => {
69 | expect(tree.find('Code')).toMatchSnapshot();
70 | });
71 |
72 | it('renders the correct code snippet', () => {
73 | expect(tree.find('Code').text()).toMatch('Code ES5 - Prototype');
74 | });
75 |
76 | it('renders a component', () => {
77 | expect(tree.find('ButtonContainer')).toBeTruthy();
78 | });
79 |
80 | it('renders with 2 buttons', () => {
81 | expect(tree.find('ButtonContainer').find('button')).toHaveLength(2);
82 | });
83 | });
84 |
85 | describe('Game page - GAME - DARK style', () => {
86 | let tree;
87 |
88 | beforeEach(() => {
89 | tree = mount(
90 |
91 |
92 |
93 |
94 |
95 | );
96 | });
97 |
98 | it('renders a component', () => {
99 | expect(tree.find('ProgressBar')).toMatchSnapshot();
100 | });
101 |
102 | it('renders a
component', () => {
103 | expect(tree.find('Code')).toMatchSnapshot();
104 | });
105 |
106 | it('renders the correct code snippet', () => {
107 | expect(tree.find('Code').text()).toMatch('Code ES5 - Prototype');
108 | });
109 |
110 | it('renders a component', () => {
111 | expect(tree.find('ButtonContainer')).toBeTruthy();
112 | });
113 |
114 | it('renders with 2 buttons', () => {
115 | expect(tree.find('ButtonContainer').find('button')).toHaveLength(2);
116 | });
117 | });
118 |
119 | describe('Game page - INTRO', () => {
120 | let tree;
121 | const store = mockStore({
122 | patterns: patterns,
123 | progress: {
124 | answers: patterns,
125 | remaining: [],
126 | current: null
127 | },
128 | intro: true,
129 | mode: 'light',
130 | js: 'es5'
131 | });
132 |
133 | beforeEach(() => {
134 | tree = mount(
135 |
136 |
137 |
138 |
139 |
140 | );
141 | });
142 |
143 | it('renders a component', () => {
144 | expect(tree.find('Button')).toMatchSnapshot();
145 | });
146 |
147 | it('reacts to button click', () => {
148 | tree.find('button').simulate('click');
149 | const actions = store.getActions();
150 | expect(actions).toMatchObject([{ type: START }]);
151 | });
152 | });
153 |
154 | describe('Game page - RESULTS', () => {
155 | let tree;
156 | const patterns = [
157 | {
158 | uuid: 'abc123',
159 | name: 'Prototype',
160 | type: 'creational',
161 | codeES5: 'Code ES5 - Prototype',
162 | codeES6: 'Code ES6 - Prototype',
163 | answered: true,
164 | answerId: 'abc123',
165 | correct: true
166 | },
167 |
168 | {
169 | uuid: 'abc234',
170 | name: 'SIngleton',
171 | type: 'creational',
172 | codeES5: 'Code ES5 - Singleton',
173 | codeES6: 'Code ES6 - Singleton',
174 | answered: true,
175 | answerId: 'abc123',
176 | correct: false
177 | }
178 | ];
179 | const store = mockStore({
180 | patterns: patterns,
181 | progress: {
182 | answers: patterns,
183 | remaining: [],
184 | current: null
185 | },
186 | intro: false,
187 | mode: 'light',
188 | js: 'es5'
189 | });
190 |
191 | beforeEach(() => {
192 | tree = mount(
193 |
194 |
195 |
196 |
197 |
198 | );
199 | });
200 |
201 | it('renders a component', () => {
202 | expect(tree.find('Result')).toMatchSnapshot();
203 | });
204 |
205 | it('renders a component', () => {
206 | expect(tree.find('Percentage')).toMatchSnapshot();
207 | });
208 |
209 | it('renders the correct percentage', () => {
210 | expect(tree.find('Percentage').text()).toMatch('5%');
211 | });
212 |
213 | it('renders a component', () => {
214 | expect(tree.find('ButtonContainer')).toMatchSnapshot();
215 | });
216 |
217 | it('reacts to button click', () => {
218 | tree.find('button').simulate('click');
219 | const actions = store.getActions();
220 | expect(actions).toMatchObject([{ type: RESTART }]);
221 | });
222 | });
223 |
--------------------------------------------------------------------------------
/__tests__/pages/Patterns.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MemoryRouter } from 'react-router-dom';
3 | import renderer from 'react-test-renderer';
4 | import { Provider } from 'react-redux';
5 | import configureMockStore from 'redux-mock-store';
6 | import 'jest-styled-components';
7 | import Patterns from '../../src/pages/Patterns';
8 |
9 | const mockStore = configureMockStore();
10 | const store = mockStore({
11 | mode: 'dark'
12 | });
13 |
14 | describe('Patterns page', () => {
15 | it('renders the Patterns list', () => {
16 | const tree = renderer
17 | .create(
18 |
19 |
20 |
21 | )
22 | .toJSON();
23 | expect(tree).toMatchSnapshot();
24 | });
25 |
26 | it('renders the individual Pattern (Singleton) info', () => {
27 | const tree = renderer
28 | .create(
29 |
30 |
31 |
32 |
33 |
34 | )
35 | .toJSON();
36 | expect(tree).toMatchSnapshot();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/__tests__/pages/__snapshots__/About.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`About page renders with a DARK theme 1`] = `
4 | .c0 {
5 | color: #C8C8C8;
6 | }
7 |
8 | .c0 a {
9 | border-bottom: 1px solid;
10 | -webkit-text-decoration: none;
11 | text-decoration: none;
12 | }
13 |
14 | .c0 a:hover {
15 | border-bottom: 1px solid transparent;
16 | }
17 |
18 | .c1 {
19 | color: #F5F5F5;
20 | margin-top: 2rem;
21 | }
22 |
23 |
26 |
29 | The Game
30 |
31 |
32 | Design Patterns - get familiar with the design patterns implemented in JavaScript, test yourself (or someone else) or simply for fun. Enjoy!
33 |
34 |
37 | References
38 |
39 |
40 | All the code samples are taken from this
41 |
42 |
43 |
47 | awesome code compilation
48 |
49 |
50 | by Felipe Beline
51 |
52 | .
53 |
54 |
55 | If you want a deep dive into the subject I can't recommend enough
56 |
57 |
58 |
62 | Learning JavaScript Design Patterns
63 |
64 |
65 | by Addy Osmani
66 |
67 | .
68 |
69 |
70 | `;
71 |
72 | exports[`About page renders with a LIGHT theme 1`] = `
73 | .c0 {
74 | color: #6F256F;
75 | }
76 |
77 | .c0 a {
78 | border-bottom: 1px solid;
79 | -webkit-text-decoration: none;
80 | text-decoration: none;
81 | }
82 |
83 | .c0 a:hover {
84 | border-bottom: 1px solid transparent;
85 | }
86 |
87 | .c1 {
88 | color: #6F256F;
89 | margin-top: 2rem;
90 | }
91 |
92 |
95 |
98 | The Game
99 |
100 |
101 | Design Patterns - get familiar with the design patterns implemented in JavaScript, test yourself (or someone else) or simply for fun. Enjoy!
102 |
103 |
106 | References
107 |
108 |
109 | All the code samples are taken from this
110 |
111 |
112 |
116 | awesome code compilation
117 |
118 |
119 | by Felipe Beline
120 |
121 | .
122 |
123 |
124 | If you want a deep dive into the subject I can't recommend enough
125 |
126 |
127 |
131 | Learning JavaScript Design Patterns
132 |
133 |
134 | by Addy Osmani
135 |
136 | .
137 |
138 |
139 | `;
140 |
--------------------------------------------------------------------------------
/__tests__/randomFromRange.test.js:
--------------------------------------------------------------------------------
1 | import { randomFromRange } from '../src/helpers/randomFromRange';
2 |
3 | describe('Helper function - select random number from range', () => {
4 | it('renders a number between 0 and 10', () => {
5 | const random = randomFromRange(10);
6 | expect(random).toBeGreaterThanOrEqual(0);
7 | expect(random).toBeLessThanOrEqual(10);
8 | });
9 |
10 | it('renders a number between 0 and 50', () => {
11 | const random = randomFromRange(50);
12 | expect(random).toBeGreaterThanOrEqual(0);
13 | expect(random).toBeLessThanOrEqual(50);
14 | });
15 |
16 | it('renders a number between 0 and 100', () => {
17 | const random = randomFromRange(100);
18 | expect(random).toBeGreaterThanOrEqual(0);
19 | expect(random).toBeLessThanOrEqual(100);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/__tests__/reducers/reducers.test.js:
--------------------------------------------------------------------------------
1 | import reducer from '../../src/reducers';
2 | import {
3 | SUBMIT,
4 | TOGGLE,
5 | TOGGLE_JS,
6 | TOGGLE_MODE,
7 | START,
8 | RESTART
9 | } from '../../src/static/constants/actions';
10 |
11 | const answers = [
12 | {
13 | name: 'Template',
14 | type: 'behavioral',
15 | answered: true,
16 | correct: true,
17 | answerId: 'ba2ca6b0-0c86-4573-baf0-60f33ce6e947',
18 | uuid: 'ba2ca6b0-0c86-4573-baf0-60f33ce6e947'
19 | },
20 | {
21 | name: 'Visitor',
22 | type: 'behavioral',
23 | answered: false,
24 | correct: null,
25 | answerId: null,
26 | uuid: 'eb9427c5-0167-4d65-a99b-a5ffadf5fd46'
27 | },
28 | {
29 | name: 'Singleton',
30 | type: 'creational',
31 | answered: false,
32 | correct: null,
33 | answerId: null,
34 | uuid: 'slearknbqarlnbqasOLdnv'
35 | }
36 | ];
37 |
38 | const initialState = {
39 | js: 'es5',
40 | mode: 'dark',
41 | patterns: answers,
42 | intro: true,
43 | progress: {
44 | answers: [],
45 | remaining: answers,
46 | current: answers[0]
47 | }
48 | };
49 |
50 | describe('Reducers', () => {
51 | it('should return the initial state', () => {
52 | expect(reducer(undefined, {})).toEqual(undefined);
53 | });
54 |
55 | it('should return unchanged state on toggle', () => {
56 | const action = {
57 | type: TOGGLE,
58 | payload: null
59 | };
60 |
61 | expect(reducer(initialState, action)).toEqual({
62 | ...initialState
63 | });
64 | });
65 |
66 | it('should toggle JS', () => {
67 | const action = {
68 | type: TOGGLE_JS,
69 | payload: 'es6'
70 | };
71 |
72 | expect(reducer(initialState, action)).toEqual({
73 | ...initialState,
74 | js: 'es6'
75 | });
76 | });
77 |
78 | it('should toggle MODE', () => {
79 | const action = {
80 | type: TOGGLE_MODE,
81 | payload: 'light'
82 | };
83 |
84 | expect(reducer(initialState, action)).toEqual({
85 | ...initialState,
86 | mode: 'light'
87 | });
88 | });
89 |
90 | it('should toggle INTRO', () => {
91 | const action = {
92 | type: START
93 | };
94 |
95 | expect(reducer(initialState, action)).toEqual({
96 | ...initialState,
97 | intro: false
98 | });
99 | });
100 |
101 | it('should handle SUBMIT', () => {
102 | const action = {
103 | type: SUBMIT,
104 | payload: {
105 | currentIndex: 1,
106 | remainingPatterns: [answers[1], answers[2]],
107 | recentlyAnswered: answers[0]
108 | }
109 | };
110 |
111 | expect(reducer(initialState, action)).toMatchObject({
112 | ...initialState,
113 | progress: {
114 | remaining: [answers[1], answers[2]],
115 | answers: [answers[0]],
116 | current: answers[2]
117 | }
118 | });
119 | });
120 |
121 | it('should handle the _last_ SUBMIT', () => {
122 | const action = {
123 | type: SUBMIT,
124 | payload: {
125 | currentIndex: null,
126 | remainingPatterns: [],
127 | recentlyAnswered: answers[2]
128 | }
129 | };
130 |
131 | expect(reducer(initialState, action)).toMatchObject({
132 | ...initialState,
133 | progress: {
134 | remaining: [],
135 | answers: [answers[2]],
136 | current: null
137 | }
138 | });
139 | });
140 |
141 | it('should handle RESTART', () => {
142 | const action = {
143 | type: RESTART,
144 | payload: null
145 | };
146 |
147 | expect(reducer(initialState, action)).toMatchObject({
148 | ...initialState,
149 | progress: {
150 | remaining: answers,
151 | answers: []
152 | }
153 | });
154 | });
155 | });
156 |
--------------------------------------------------------------------------------
/__tests__/selectors/selectors.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | getRemaining,
3 | getPatterns,
4 | getMode,
5 | getJS,
6 | getCurrent,
7 | getAnswers,
8 | getIntro
9 | } from '../../src/selectors';
10 |
11 | describe('Selectors', () => {
12 | let state;
13 |
14 | beforeEach(() => {
15 | state = {
16 | mode: 'dark',
17 | js: 'es6',
18 | intro: true,
19 | patterns: [0, 1, 2, 3, 4, 5, 6],
20 | progress: {
21 | answers: [0, 1, 2],
22 | remaining: [4, 5, 6],
23 | current: 3
24 | }
25 | };
26 | });
27 |
28 | it('should get the current pattern', () => {
29 | expect(getCurrent(state)).toEqual(3);
30 | });
31 |
32 | it('should get the remaining patterns', () => {
33 | expect(getRemaining(state)).toEqual([4, 5, 6]);
34 | });
35 |
36 | it('should get the all patterns', () => {
37 | expect(getPatterns(state)).toEqual([0, 1, 2, 3, 4, 5, 6]);
38 | });
39 |
40 | it('should get the answers', () => {
41 | expect(getAnswers(state)).toEqual([0, 1, 2]);
42 | });
43 |
44 | it('should get the theme mode', () => {
45 | expect(getMode(state)).toBe('dark');
46 | });
47 |
48 | it('should get the JS version', () => {
49 | expect(getJS(state)).toBe('es6');
50 | });
51 |
52 | it('should get the intro value', () => {
53 | expect(getIntro(state)).toBe(true);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/__tests__/store/store.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | TOGGLE_JS,
3 | TOGGLE_MODE
4 | } from '../../src/static/constants/actions';
5 | import { loadState, saveState } from '../../src/helpers/localStorage';
6 |
7 | describe('Store', () => {
8 | beforeEach(() => {
9 | jest.resetModules();
10 | window.localStorage.clear();
11 | });
12 |
13 | it('should work without saved state', () => {
14 | const {
15 | default: store,
16 | initialState
17 | } = require('../../src/store');
18 |
19 | expect(store.getState()).toMatchObject(initialState);
20 | });
21 |
22 | it('should load saved state from localStorage', () => {
23 | const savedState = {
24 | mode: 'light',
25 | js: 'es6'
26 | };
27 | saveState(savedState);
28 |
29 | const state = require('../../src/store').default.getState();
30 | expect(state.mode).toBe('light');
31 | expect(state.js).toBe('es6');
32 | });
33 |
34 | it('should save state to localStorage', () => {
35 | const toggleJSAction = {
36 | type: TOGGLE_JS,
37 | payload: 'es6'
38 | };
39 |
40 | const toggleModeAction = {
41 | type: TOGGLE_MODE,
42 | payload: 'light'
43 | };
44 |
45 | const {
46 | default: store
47 | } = require('../../src/store');
48 |
49 | store.dispatch(toggleJSAction);
50 | store.dispatch(toggleModeAction);
51 |
52 | expect(loadState()).toMatchObject({
53 | mode: 'light',
54 | js: 'es6'
55 | });
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/__tests__/styles/__snapshots__/global.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Global style renders with a DARK theme 1`] = `
4 |
5 |
14 |
15 | `;
16 |
17 | exports[`Global style renders with a LIGHT theme 1`] = `
18 |
19 |
28 |
29 | `;
30 |
--------------------------------------------------------------------------------
/__tests__/styles/global.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import 'jest-styled-components';
4 | import { themeLight } from '../../src/styles/themes/theme.light';
5 | import { themeDark } from '../../src/styles/themes/theme.dark';
6 | import GlobalStyle from '../../src/styles/global';
7 |
8 | describe('Global style', () => {
9 | it('renders with a LIGHT theme', () => {
10 | renderer.create( );
11 | expect(document.head).toMatchSnapshot();
12 | });
13 |
14 | it('renders with a DARK theme', () => {
15 | renderer.create( );
16 | expect(document.head).toMatchSnapshot();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "design-patterns-javascript"
3 | }
4 |
--------------------------------------------------------------------------------
/cypress/integration/about.spec.js:
--------------------------------------------------------------------------------
1 | /* globals context, cy */
2 | context('About', () => {
3 | beforeEach(() => {
4 | cy.visit('http://localhost:8080/about');
5 | });
6 |
7 | describe('Header', () => {
8 | it('should have 2 navigation links', () => {
9 | cy.get('header a').should('have.length', 2);
10 | });
11 |
12 | it('should have _Game_ link', () => {
13 | cy.get('header a')
14 | .first()
15 | .should('have.text', 'Game')
16 | .should('have.attr', 'href')
17 | .and('include', '/');
18 | });
19 |
20 | it('should have _Pattern Reference_ link', () => {
21 | cy.get('header a:last')
22 | .first()
23 | .should('have.text', 'Pattern Reference')
24 | .should('have.attr', 'href')
25 | .and('include', '/patterns');
26 | });
27 |
28 | it('should have the current page title in a span', () => {
29 | cy.get('header span')
30 | .should('exist')
31 | .should('have.text', 'About');
32 | });
33 |
34 | it('should have the MODE switch', () => {
35 | cy.get('header button[data-cy=mode]').should('exist');
36 | cy.get('header button[data-cy=mode]').should('be.visible');
37 | });
38 |
39 | it('should NOT have the JS switch', () => {
40 | cy.get('header button[data-cy=js]').should('not.exist');
41 | });
42 | });
43 |
44 | describe('Content', () => {
45 | it('should have 2 section headers', () => {
46 | cy.get('h3').should('have.length', 2);
47 |
48 | cy.get('h3:first').should('have.text', 'The Game');
49 | cy.get('h3:last').should('have.text', 'References');
50 | });
51 | });
52 |
53 | describe('Behavior', () => {
54 | it('should switch the mode', () => {
55 | cy.get('body').should('have.css', 'background-color', 'rgb(34, 34, 34)');
56 | cy.get('header button[data-cy=mode]').click();
57 | cy.get('body').should('have.css', 'background-color', 'rgb(255, 255, 255)');
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "build",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Design Patterns - JavaScript
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { BrowserRouter as Router } from 'react-router-dom';
5 | import store from './src/store';
6 | import Layout from './src/Layout';
7 | import ScrollToTop from './src/components/ScrollToTop';
8 |
9 | const App = () => (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | ReactDOM.render( , document.getElementById('root'));
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "design-patterns-game",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "prop-types": "15.7.2",
6 | "react": "^16.9.0",
7 | "react-dom": "^16.9.0",
8 | "react-redux": "7.1.3",
9 | "react-router-dom": "5.1.0",
10 | "react-syntax-highlighter": "^10.3.5",
11 | "redux": "4.0.1",
12 | "styled-components": "^4.3.1",
13 | "uuid": "3.3.3"
14 | },
15 | "scripts": {
16 | "start": "webpack-dev-server --mode development",
17 | "start:ci": "npm start & wait-on http://localhost:8080",
18 | "format": "prettier --write \"src/**/*.js\"",
19 | "stats": "webpack-bundle-analyzer stats/stats.json",
20 | "build": "webpack --mode production",
21 | "test": "jest",
22 | "test:watch": "jest --watch",
23 | "test:coverage": "jest --coverage",
24 | "cypress:open": "cypress open",
25 | "cypress:run": "cypress run",
26 | "storybook": "start-storybook -p 6006",
27 | "storybook:build": "build-storybook"
28 | },
29 | "jest": {
30 | "setupFiles": [
31 | "__tests__/config/configure-enzyme.js",
32 | "__tests__/config/register-context.js"
33 | ],
34 | "snapshotSerializers": [
35 | "enzyme-to-json/serializer"
36 | ],
37 | "testPathIgnorePatterns": [
38 | "/__tests__/config/",
39 | "/__tests__/helpers/",
40 | "/cypress/"
41 | ]
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.7.4",
45 | "@babel/plugin-proposal-class-properties": "7.7.4",
46 | "@babel/preset-env": "^7.7.4",
47 | "@babel/preset-react": "7.7.4",
48 | "@storybook/addon-actions": "^5.1.9",
49 | "@storybook/addon-knobs": "^5.1.9",
50 | "@storybook/addon-links": "^5.1.9",
51 | "@storybook/addon-storyshots": "^5.1.9",
52 | "@storybook/addons": "^5.1.9",
53 | "@storybook/react": "^5.1.9",
54 | "babel-eslint": "^10.0.2",
55 | "babel-jest": "^24.9.0",
56 | "babel-loader": "^8.0.6",
57 | "babel-plugin-require-context-hook": "1.0.0",
58 | "babel-preset-env": "1.7.0",
59 | "babel-preset-react": "6.24.1",
60 | "coveralls": "3.0.8",
61 | "cypress": "3.6.1",
62 | "enzyme": "3.10.0",
63 | "enzyme-adapter-react-16": "^1.14.0",
64 | "enzyme-to-json": "3.4.3",
65 | "eslint": "^6.0.1",
66 | "eslint-config-react": "1.1.7",
67 | "eslint-loader": "3.0.3",
68 | "eslint-plugin-react": "^7.14.2",
69 | "html-webpack-plugin": "3.2.0",
70 | "jest": "^24.9.0",
71 | "jest-styled-components": "^6.3.3",
72 | "prettier": "^1.17.1",
73 | "react-test-renderer": "16.8.6",
74 | "redux-mock-store": "1.5.4",
75 | "storybook-addon-styled-component-theme": "1.2.5",
76 | "wait-on": "3.3.0",
77 | "webpack": "^4.35.3",
78 | "webpack-bundle-analyzer": "3.6.0",
79 | "webpack-cli": "^3.3.2",
80 | "webpack-dev-server": "^3.4.1"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
4 | import { ThemeProvider } from 'styled-components';
5 | import { connect } from 'react-redux';
6 | import styleLight from './styles/hljs/hljs.light';
7 | import styleDark from './styles/hljs/hljs.dark';
8 | import Header from './components/Header';
9 | import Footer from './components/Footer';
10 | import GlobalStyle from './styles/global';
11 | import { themeCommon } from './styles/themes/theme.common';
12 | import { themeLight } from './styles/themes/theme.light';
13 | import { themeDark } from './styles/themes/theme.dark';
14 | import Game from './pages/Game';
15 | import About from './pages/About';
16 | import Patterns from './pages/Patterns';
17 | import { getMode } from './selectors';
18 |
19 | const Layout = props => {
20 | const { mode } = props;
21 |
22 | let style = styleLight;
23 | let theme = { ...themeCommon, ...themeLight };
24 |
25 | if (props.mode === 'dark') {
26 | style = styleDark;
27 | theme = { ...themeCommon, ...themeDark };
28 | }
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 | } />
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | Layout.propTypes = {
50 | mode: PropTypes.string.isRequired
51 | };
52 |
53 | export default withRouter(
54 | connect(state => ({
55 | mode: getMode(state)
56 | }))(Layout)
57 | );
58 |
--------------------------------------------------------------------------------
/src/actions/restart.js:
--------------------------------------------------------------------------------
1 | import { RESTART } from '../static/constants/actions';
2 |
3 | export const restart = payload => ({
4 | type: RESTART,
5 | payload
6 | });
7 |
--------------------------------------------------------------------------------
/src/actions/start.js:
--------------------------------------------------------------------------------
1 | import { START } from '../static/constants/actions';
2 |
3 | export const start = payload => ({
4 | type: START,
5 | payload
6 | });
7 |
--------------------------------------------------------------------------------
/src/actions/submitAnswer.js:
--------------------------------------------------------------------------------
1 | import { SUBMIT } from '../static/constants/actions';
2 |
3 | export const submitAnswer = payload => ({
4 | type: SUBMIT,
5 | payload
6 | });
7 |
--------------------------------------------------------------------------------
/src/actions/toggle.js:
--------------------------------------------------------------------------------
1 | import { TOGGLE, TOGGLE_JS, TOGGLE_MODE } from '../static/constants/actions';
2 |
3 | export const toggle = payload => ({
4 | type: TOGGLE,
5 | payload
6 | });
7 |
8 | export const toggleJS = payload => ({
9 | type: TOGGLE_JS,
10 | payload
11 | });
12 |
13 | export const toggleMode = payload => ({
14 | type: TOGGLE_MODE,
15 | payload
16 | });
17 |
--------------------------------------------------------------------------------
/src/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | const StyledButton = styled.button`
6 | background-color: ${props => props.theme.buttonBackground};
7 | border: 1px solid ${props => props.theme.buttonBorder};
8 | border-radius: 4px;
9 | cursor: pointer;
10 | font: 400 1rem 'Karla', 'sans-serif';
11 | height: 48px;
12 | max-width: 240px;
13 | outline: none;
14 | padding: 0 1.5rem;
15 | width: 37.5%;
16 |
17 | & span {
18 | color: ${props => props.theme.buttonColor};
19 | }
20 |
21 | &:hover {
22 | background-color: ${props => props.theme.buttonBackgroundHover};
23 | border-color: ${props => props.theme.buttonBorderHover};
24 |
25 | & span {
26 | color: ${props => props.theme.buttonColorHover};
27 | }
28 | }
29 | `;
30 |
31 | export const Button = props => {
32 | const { id, label, onClick } = props;
33 |
34 | return (
35 |
36 | {label && {label} }
37 |
38 | );
39 | };
40 |
41 | Button.propTypes = {
42 | label: PropTypes.string.isRequired,
43 | id: PropTypes.string.isRequired,
44 | onClick: PropTypes.func.isRequired
45 | };
46 |
47 | export default Button;
48 |
--------------------------------------------------------------------------------
/src/components/ButtonContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { connect } from 'react-redux';
5 | import Button from './Button';
6 | import { submitAnswer } from '../actions/submitAnswer';
7 | import { getCurrent, getPatterns } from '../selectors';
8 | import { shuffle } from '../helpers/shuffleArray';
9 |
10 | const StyledButtonContainer = styled.div`
11 | align-content: space-around;
12 | display: flex;
13 | flex-wrap: wrap;
14 | height: 9rem;
15 | justify-content: space-around;
16 | margin: 1rem 0 2rem;
17 | `;
18 |
19 | export const ButtonContainer = props => {
20 | const { current, patterns, onSubmitAnswer } = props;
21 |
22 | // get 3 random patterns in addition to correct one
23 | const allOtherAnswers = patterns.filter(pattern => pattern.uuid !== current.uuid);
24 | const additional = shuffle(allOtherAnswers).slice(0, 3);
25 | // shuffle the 4 answers
26 | const variants = shuffle([current, ...additional]);
27 |
28 | return (
29 |
30 | {variants.map(({ uuid, name }) => (
31 | onSubmitAnswer(uuid)} />
32 | ))}
33 |
34 | );
35 | };
36 |
37 | ButtonContainer.propTypes = {
38 | patterns: PropTypes.array.isRequired,
39 | current: PropTypes.object.isRequired,
40 | onSubmitAnswer: PropTypes.func.isRequired
41 | };
42 |
43 | export default connect(
44 | state => ({
45 | current: getCurrent(state),
46 | patterns: getPatterns(state)
47 | }),
48 | {
49 | onSubmitAnswer: id => submitAnswer(id)
50 | }
51 | )(ButtonContainer);
52 |
--------------------------------------------------------------------------------
/src/components/Code.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
5 | import js from 'react-syntax-highlighter/dist/languages/hljs/javascript';
6 | import { getJS, getCurrent } from '../selectors';
7 | import CodePreTag from './CodePreTag';
8 |
9 | SyntaxHighlighter.registerLanguage('javascript', js);
10 |
11 | const Code = props => {
12 | const { js, current, style } = props;
13 |
14 | return (
15 |
16 | {js === 'es5' && (
17 |
18 | {current.codeES5}
19 |
20 | )}
21 |
22 | {js === 'es6' && (
23 |
24 | {current.codeES6}
25 |
26 | )}
27 |
28 | );
29 | };
30 |
31 | Code.propTypes = {
32 | js: PropTypes.string.isRequired,
33 | current: PropTypes.object.isRequired,
34 | style: PropTypes.object.isRequired
35 | };
36 |
37 | export default connect(state => ({
38 | js: getJS(state),
39 | current: getCurrent(state)
40 | }))(Code);
41 |
--------------------------------------------------------------------------------
/src/components/CodePreTag.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const CodePreTag = ({ children, ...restProps }) => {
5 | const syntaxHighlighterEl = useRef(null);
6 |
7 | useEffect(() => {
8 | if (syntaxHighlighterEl.current) {
9 | syntaxHighlighterEl.current.scrollTop = 0;
10 | syntaxHighlighterEl.current.scrollLeft = 0;
11 | }
12 | }, [children]);
13 |
14 | return {children} ;
15 | };
16 |
17 | CodePreTag.propTypes = {
18 | children: PropTypes.node
19 | };
20 |
21 | export default CodePreTag;
22 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import {
4 | ICON_TWITTER,
5 | ICON_FACEBOOK,
6 | ICON_LINKEDIN,
7 | ICON_REDDIT,
8 | ICON_GITHUB
9 | } from '../static/icons';
10 |
11 | const FooterContainer = styled.footer`
12 | display: flex;
13 | justify-content: center;
14 | margin-top: 5rem;
15 |
16 | a {
17 | border: 1px solid ${props => props.theme.buttonBackground};
18 | border-radius: 50%;
19 | display: inline-flex;
20 | margin: 0.75rem;
21 | padding: 0.5rem;
22 |
23 | :hover svg,
24 | :focus svg {
25 | fill: ${props => props.theme.toggleFillHover};
26 | }
27 | }
28 |
29 | svg {
30 | fill: ${props => props.theme.buttonBackground};
31 | transition: 0.2s;
32 | }
33 | `;
34 |
35 | const Footer = () => (
36 |
37 |
41 | {ICON_TWITTER}
42 |
43 |
44 | {ICON_FACEBOOK}
45 |
46 |
50 | {ICON_LINKEDIN}
51 |
52 |
56 | {ICON_REDDIT}
57 |
58 |
59 | {ICON_GITHUB}
60 |
61 |
62 | );
63 |
64 | export default Footer;
65 |
--------------------------------------------------------------------------------
/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { css } from 'styled-components';
4 | import { Route, withRouter, NavLink as Link } from 'react-router-dom';
5 | import Toggle from './Toggle';
6 | import Title from './Title';
7 |
8 | const StyledHeader = styled.header`
9 | align-items: center;
10 | display: flex;
11 | flex-wrap: wrap;
12 | justify-content: start;
13 | margin-top: 1rem;
14 |
15 | @media (min-width: 769px) {
16 | justify-content: space-between;
17 | }
18 | `;
19 |
20 | const StyledLinkContainer = styled.div`
21 | display: inline-flex;
22 | `;
23 |
24 | const StyledSettingsContainer = styled.div`
25 | display: inline-flex;
26 | margin: 1rem 0;
27 | width: 100%;
28 |
29 | @media (min-width: 541px) {
30 | margin: 0;
31 | width: auto;
32 | }
33 | `;
34 |
35 | const linkStyle = css`
36 | border-bottom: 1px solid ${props => props.theme.link};
37 | color: ${props => props.theme.link};
38 | display: inline-flex;
39 | font-size: 0.875rem;
40 | margin: 0.5rem 2rem 0 0;
41 | padding-bottom: 1px;
42 | text-decoration: none;
43 | `;
44 |
45 | const StyledRouterLink = styled(Link)`
46 | ${linkStyle}
47 | &:hover {
48 | border-bottom: none;
49 | }
50 | `;
51 |
52 | const StyledRouterSpan = styled.span`
53 | ${linkStyle}
54 | border-bottom: none;
55 | color: ${props => props.theme.active};
56 | `;
57 |
58 | const Header = props => {
59 | const {
60 | location: { pathname }
61 | } = props;
62 |
63 | const paths = [
64 | {
65 | path: '/',
66 | page: 'Game'
67 | },
68 | {
69 | path: '/patterns',
70 | page: 'Pattern Reference'
71 | },
72 | {
73 | path: '/about',
74 | page: 'About'
75 | }
76 | ];
77 |
78 | return (
79 |
80 |
81 | {paths.map(({ path, page }) =>
82 | pathname === path || (path === '/patterns' && pathname.includes(path)) ? (
83 | {page}
84 | ) : (
85 |
86 | {page}
87 |
88 | )
89 | )}
90 |
91 |
92 |
93 | } />
94 |
95 |
96 |
97 |
98 |
99 | );
100 | };
101 |
102 | Header.propTypes = {
103 | location: PropTypes.object.isRequired
104 | };
105 |
106 | export default withRouter(Header);
107 |
--------------------------------------------------------------------------------
/src/components/Pattern.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 | import { Link } from 'react-router-dom';
6 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
7 | import js from 'react-syntax-highlighter/dist/languages/hljs/javascript';
8 | import styleLight from '../styles/hljs/hljs.light';
9 | import styleDark from '../styles/hljs/hljs.dark';
10 | import { patterns } from '../static/patterns';
11 | import { restart } from '../actions/restart';
12 | import { getMode } from '../selectors';
13 |
14 | SyntaxHighlighter.registerLanguage('javascript', js);
15 |
16 | const StyledPattern = styled.div`
17 | color: ${props => props.theme.text};
18 |
19 | h2,
20 | h3 {
21 | color: ${props => props.theme.header};
22 | margin-top: 2.5rem;
23 | }
24 | `;
25 |
26 | const SubHeader = styled.span`
27 | background-color: ${props => props.theme.titleBackground};
28 | color: ${props => props.theme.header};
29 | display: block;
30 | margin-bottom: 8px;
31 | padding: 4px;
32 | text-transform: uppercase;
33 | `;
34 |
35 | const Type = styled.span`
36 | text-transform: capitalize;
37 | `;
38 |
39 | const StyledLink = styled(Link)`
40 | border-bottom: 1px solid ${props => props.theme.CRIMSON};
41 | color: ${props => props.theme.CRIMSON};
42 | display: inline-block;
43 | margin-top: 2rem;
44 | text-decoration: none;
45 |
46 | &:hover {
47 | border-bottom: 1px solid transparent;
48 | }
49 | `;
50 |
51 | class Pattern extends React.Component {
52 | componentDidMount() {
53 | this.props.reset();
54 | }
55 |
56 | render() {
57 | const {
58 | params: { id }
59 | } = this.props.match;
60 |
61 | const pattern = patterns.filter(item => item.id === id)[0];
62 |
63 | const style = this.props.mode === 'dark' ? styleDark : styleLight;
64 |
65 | return (
66 |
67 | ← Back to Patterns List
68 | {pattern && (
69 |
70 | {pattern.name}
71 |
72 | Type:
73 | {pattern.type} pattern
74 |
75 |
76 | Definition:
77 | {pattern.definition}
78 |
79 | {pattern.when && (
80 |
81 | Use when…
82 | …{pattern.when}.
83 |
84 | )}
85 |
86 | ES6
87 |
88 | {pattern.codeES6}
89 |
90 |
91 | ES5
92 |
93 | {pattern.codeES5}
94 |
95 |
96 | )}
97 |
98 | );
99 | }
100 | }
101 |
102 | Pattern.propTypes = {
103 | match: PropTypes.object.isRequired,
104 | mode: PropTypes.string.isRequired,
105 | reset: PropTypes.func.isRequired
106 | };
107 |
108 | export default connect(
109 | state => ({
110 | mode: getMode(state)
111 | }),
112 | {
113 | reset: () => restart()
114 | }
115 | )(Pattern);
116 |
--------------------------------------------------------------------------------
/src/components/PatternsList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | import { patterns } from '../static/patterns';
5 |
6 | const StyledPatterns = styled.div`
7 | color: ${props => props.theme.text};
8 |
9 | a {
10 | border-bottom: 1px solid transparent;
11 | color: ${props => props.theme.CRIMSON};
12 | text-decoration: none;
13 |
14 | &:hover {
15 | border-bottom: 1px solid ${props => props.theme.CRIMSON};
16 | }
17 | }
18 |
19 | h2,
20 | h3 {
21 | color: ${props => props.theme.header};
22 | margin-top: 2.5rem;
23 | }
24 |
25 | h3 {
26 | border-bottom: 1px solid ${props => props.theme.text};
27 | color: ${props => props.theme.header};
28 | padding-bottom: 1rem;
29 | }
30 | `;
31 |
32 | const PatternsList = () => {
33 | const lister = patternType => (
34 |
35 | {patterns.map(({ id, name, type }) => {
36 | if (type === patternType) {
37 | return (
38 |
39 | {name}
40 |
41 | );
42 | }
43 | })}
44 |
45 | );
46 |
47 | return (
48 |
49 | Design Patterns
50 |
51 |
52 | In software engineering, a design pattern is a general repeatable solution to a commonly
53 | occurring problem in software design.
54 |
55 |
56 | Creational Design Patterns
57 |
58 | These design patterns are all about class instantiation. This pattern can be further divided
59 | into class-creation patterns and object-creational patterns. While class-creation patterns
60 | use inheritance effectively in the instantiation process, object-creation patterns use
61 | delegation effectively to get the job done.
62 |
63 | {lister('creational')}
64 |
65 | Structural Design Patterns
66 |
67 | These design patterns are all about Class and Object composition. Structural class-creation
68 | patterns use inheritance to compose interfaces. Structural object-patterns define ways to
69 | compose objects to obtain new functionality.
70 |
71 | {lister('structural')}
72 |
73 | Behavioral Design Patterns
74 |
75 | These design patterns are all about Class's objects communication. Behavioral patterns are
76 | those patterns that are most specifically concerned with communication between objects.
77 |
78 | {lister('behavioral')}
79 |
80 | );
81 | };
82 |
83 | export default PatternsList;
84 |
--------------------------------------------------------------------------------
/src/components/Percentage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { connect } from 'react-redux';
5 | import { getAnswers } from '../selectors';
6 |
7 | const StyledPercentage = styled.h1`
8 | border: 4px solid ${props => props.level};
9 | border-radius: 50%;
10 | color: ${props => props.level};
11 | font-family: 'Karla', 'sans-serif';
12 | font-size: 4rem;
13 | height: 10rem;
14 | margin: auto;
15 | line-height: 10rem;
16 | text-align: center;
17 | width: 10rem;
18 | `;
19 |
20 | export const Percentage = ({ answers }) => {
21 | let level = 'red';
22 | let correct = 0;
23 |
24 | answers.map(answer => (answer.correct ? correct++ : null));
25 |
26 | const percent = Math.ceil((correct * 100) / 23);
27 |
28 | switch (true) {
29 | case percent >= 70:
30 | level = 'limegreen';
31 | break;
32 | case percent >= 40:
33 | level = 'orange';
34 | break;
35 | }
36 |
37 | return {percent}% ;
38 | };
39 |
40 | Percentage.propTypes = {
41 | answers: PropTypes.array.isRequired
42 | };
43 |
44 | export default connect(state => ({
45 | answers: getAnswers(state)
46 | }))(Percentage);
47 |
--------------------------------------------------------------------------------
/src/components/ProgressBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { connect } from 'react-redux';
5 | import { getAnswers, getRemaining } from '../selectors';
6 |
7 | const Container = styled.div`
8 | display: flex;
9 | height: 3px;
10 | justify-content: space-between;
11 | margin: 1.25rem 0 1rem;
12 | width: 100%;
13 | `;
14 |
15 | const Step = styled.span`
16 | background: ${props => (props.nature ? props.theme[props.nature] : props.theme.ALTO)};
17 | display: flex;
18 | height: 2px;
19 | width: 3.75%;
20 | `;
21 |
22 | export const ProgressBar = props => {
23 | const { answers, remaining } = props;
24 |
25 | return (
26 |
27 | {answers.map(({ uuid, answered, correct }) => {
28 | let nature;
29 |
30 | if (answered && correct) {
31 | nature = 'ATLANTIS';
32 | } else {
33 | nature = 'CRIMSON';
34 | }
35 |
36 | return ;
37 | })}
38 |
39 | {remaining.map(({ uuid }) => (
40 |
41 | ))}
42 |
43 | );
44 | };
45 |
46 | ProgressBar.propTypes = {
47 | answers: PropTypes.array.isRequired,
48 | remaining: PropTypes.array.isRequired
49 | };
50 |
51 | export default connect(state => ({
52 | answers: getAnswers(state),
53 | remaining: getRemaining(state)
54 | }))(ProgressBar);
55 |
--------------------------------------------------------------------------------
/src/components/Result.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { connect } from 'react-redux';
5 | import { getAnswers } from '../selectors';
6 |
7 | const StyledResult = styled.p`
8 | color: ${props => props.theme.text};
9 | margin: 3rem 0;
10 | text-align: center;
11 | `;
12 |
13 | export const Result = ({ answers }) => {
14 | let correct = 0;
15 |
16 | answers.map(answer => (answer.correct ? correct++ : null));
17 |
18 | return (
19 |
20 | You got {correct} patterns right out of {answers.length}.
21 |
22 | );
23 | };
24 |
25 | Result.propTypes = {
26 | answers: PropTypes.array.isRequired
27 | };
28 |
29 | export default connect(state => ({
30 | answers: getAnswers(state)
31 | }))(Result);
32 |
--------------------------------------------------------------------------------
/src/components/ScrollToTop.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withRouter } from 'react-router-dom';
4 |
5 | class ScrollToTop extends Component {
6 | componentDidUpdate(prevProps) {
7 | if (this.props.location.pathname !== prevProps.location.pathname) {
8 | window.scrollTo(0, 0);
9 | }
10 | }
11 |
12 | render() {
13 | return this.props.children;
14 | }
15 | }
16 |
17 | ScrollToTop.propTypes = {
18 | location: PropTypes.object.isRequired,
19 | children: PropTypes.object.isRequired
20 | };
21 |
22 | export default withRouter(ScrollToTop);
23 |
--------------------------------------------------------------------------------
/src/components/Svg.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { getMode } from '../selectors';
5 | import { JS, SUN_OUTLINED, SUN_FILLED } from '../static/icons';
6 |
7 | const SVG = ({ control, mode }) => {
8 | let icon = JS;
9 |
10 | if (control === 'mode') {
11 | icon = mode === 'dark' ? SUN_OUTLINED : SUN_FILLED;
12 | }
13 |
14 | return (
15 |
22 | {icon}
23 |
24 | );
25 | };
26 |
27 | SVG.propTypes = {
28 | control: PropTypes.string.isRequired,
29 | mode: PropTypes.string.isRequired
30 | };
31 |
32 | export default connect(state => ({
33 | mode: getMode(state)
34 | }))(SVG);
35 |
--------------------------------------------------------------------------------
/src/components/Title.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const TitleContainer = styled.div`
5 | padding: 0.75rem 0 1rem;
6 | border-radius: 4px;
7 | background: ${props => props.theme.titleBackground};
8 | margin: 1rem 0 0;
9 | text-align: center;
10 | width: 100%;
11 | `;
12 |
13 | const Heading = styled.h1`
14 | font-family: 'Karla', sans-serif;
15 | font-size: 1.75rem;
16 | color: ${props => props.theme.title};
17 | margin: 0;
18 | `;
19 |
20 | const SubHeading = styled.h2`
21 | font: 400 0.875rem 'Karla', sans-serif;
22 | color: ${props => props.theme.link};
23 | margin: 0.75rem 0 0;
24 | `;
25 |
26 | export const Title = () => (
27 |
28 | Design Patterns Game
29 | “Gang of Four” patterns in JavaScript
30 |
31 | );
32 |
33 | export default Title;
34 |
--------------------------------------------------------------------------------
/src/components/Toggle.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { connect } from 'react-redux';
5 | import { toggle } from '../actions/toggle';
6 | import { getJS, getMode } from '../selectors';
7 | import SVG from './Svg';
8 |
9 | const StyledToggle = styled.button`
10 | background-color: ${props => props.theme.toggleBackground};
11 | border: 1px solid ${props => props.theme.toggleBorder};
12 | border-radius: 50%;
13 | cursor: pointer;
14 | height: 2.5rem;
15 | margin: 0.5rem 1.5rem 0 0;
16 | outline: 0;
17 | width: 2.5rem;
18 | z-index: 10;
19 |
20 | @media (min-width: 769px) {
21 | margin: 0 0 0 1.5rem;
22 | }
23 |
24 | & svg,
25 | & g {
26 | fill: ${props => props.theme.toggleFill};
27 | }
28 |
29 | &:hover {
30 | background-color: ${props => props.theme.toggleBackgroundHover};
31 |
32 | & svg,
33 | & g {
34 | fill: ${props => props.theme.toggleFillHover};
35 | }
36 | }
37 |
38 | &.active {
39 | background-color: ${props => props.theme.toggleActiveBackground};
40 | border-color: ${props => props.theme.toggleActiveBorder};
41 |
42 | & svg,
43 | & g {
44 | fill: ${props => props.theme.toggleActiveFill};
45 | }
46 | }
47 | `;
48 |
49 | export const Toggle = props => {
50 | const { onToggle, control, js, mode } = props;
51 |
52 | let isActive;
53 | if (control === 'js' && js === 'es6') isActive = 'active';
54 | if (control === 'mode' && mode === 'light') isActive = 'active';
55 |
56 | return (
57 | onToggle(control)} className={isActive} data-cy={control}>
58 |
59 |
60 | );
61 | };
62 |
63 | Toggle.propTypes = {
64 | onToggle: PropTypes.func.isRequired,
65 | control: PropTypes.string.isRequired,
66 | mode: PropTypes.string.isRequired,
67 | js: PropTypes.string.isRequired
68 | };
69 |
70 | export default connect(
71 | state => ({
72 | js: getJS(state),
73 | mode: getMode(state)
74 | }),
75 | {
76 | onToggle: control => toggle(control)
77 | }
78 | )(Toggle);
79 |
--------------------------------------------------------------------------------
/src/helpers/localStorage.js:
--------------------------------------------------------------------------------
1 | export const loadState = () => {
2 | try {
3 | const serializedState = JSON.parse(localStorage.getItem('state'));
4 |
5 | return serializedState === null ? undefined : serializedState;
6 | } catch (e) {
7 | return undefined;
8 | }
9 | };
10 |
11 | export const saveState = (state) => {
12 | try {
13 | const serializedState = JSON.stringify(state);
14 | localStorage.setItem('state', serializedState);
15 | } catch (e) {
16 | return undefined;
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/src/helpers/randomFromRange.js:
--------------------------------------------------------------------------------
1 | // [0, max] range
2 | export const randomFromRange = max => Math.floor(Math.random() * max);
3 |
--------------------------------------------------------------------------------
/src/helpers/shuffleArray.js:
--------------------------------------------------------------------------------
1 | export const shuffle = array => array.sort(() => 0.5 - Math.random());
2 |
--------------------------------------------------------------------------------
/src/middleware/index.js:
--------------------------------------------------------------------------------
1 | import { toggleMiddleware } from './toggle';
2 | import { submitMiddleware } from './submit';
3 |
4 | const middleware = [toggleMiddleware, submitMiddleware];
5 |
6 | export default middleware;
7 |
--------------------------------------------------------------------------------
/src/middleware/submit.js:
--------------------------------------------------------------------------------
1 | import { SUBMIT } from '../static/constants/actions';
2 | import { randomFromRange } from '../helpers/randomFromRange';
3 |
4 | export const submitMiddleware = ({ getState }) => next => action => {
5 | if (action.type === SUBMIT) {
6 | const { progress } = getState();
7 |
8 | // remove code fields - not necessary in progress.answers
9 | const filteredKeys = ['uuid', 'name', 'type'];
10 | const filtered = filteredKeys.reduce(
11 | (obj, key) => ({ ...obj, [key]: progress.current[key] }),
12 | {}
13 | );
14 |
15 | const recentlyAnswered = {
16 | ...filtered,
17 | answered: true,
18 | answerId: action.payload,
19 | correct: action.payload === progress.current.uuid
20 | };
21 |
22 | const remainingPatterns = progress.remaining.filter(
23 | pattern => pattern.uuid !== progress.current.uuid
24 | );
25 |
26 | const currentIndex = randomFromRange(remainingPatterns.length);
27 |
28 | action.payload = {
29 | recentlyAnswered,
30 | remainingPatterns,
31 | currentIndex
32 | };
33 | }
34 |
35 | next(action);
36 | };
37 |
--------------------------------------------------------------------------------
/src/middleware/toggle.js:
--------------------------------------------------------------------------------
1 | import { TOGGLE } from '../static/constants/actions';
2 | import { toggleJS, toggleMode } from '../actions/toggle';
3 |
4 | export const toggleMiddleware = ({ dispatch, getState }) => next => action => {
5 | if (action.type === TOGGLE) {
6 | switch (action.payload) {
7 | case 'js':
8 | const js = getState()['js'] === 'es5' ? 'es6' : 'es5';
9 | dispatch(toggleJS(js));
10 | break;
11 | case 'mode':
12 | const mode = getState()['mode'] === 'dark' ? 'light' : 'dark';
13 | dispatch(toggleMode(mode));
14 | break;
15 | }
16 | }
17 |
18 | next(action);
19 | };
20 |
--------------------------------------------------------------------------------
/src/pages/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const StyledAbout = styled.div`
5 | color: ${props => props.theme.text};
6 |
7 | a {
8 | border-bottom: 1px solid ${props => props.theme.CRIMSON};
9 | color: ${props => props.theme.CRIMSON};
10 | text-decoration: none;
11 |
12 | &:hover {
13 | border-bottom: 1px solid transparent;
14 | }
15 | }
16 | `;
17 |
18 | const Header = styled.h3`
19 | color: ${props => props.theme.header};
20 | margin-top: 2rem;
21 | `;
22 |
23 | const About = () => (
24 |
25 |
26 |
27 | Design Patterns - get familiar with the design patterns implemented in JavaScript, test
28 | yourself (or someone else) or simply for fun. Enjoy!
29 |
30 |
31 |
32 |
33 | All the code samples are taken from this{' '}
34 |
35 |
36 | awesome code compilation
37 | {' '}
38 | by Felipe Beline
39 |
40 | .
41 |
42 |
43 | If you want a deep dive into the subject I can't recommend enough{' '}
44 |
45 |
46 | Learning JavaScript Design Patterns
47 | {' '}
48 | by Addy Osmani
49 |
50 | .
51 |
52 |
53 | );
54 |
55 | export default About;
56 |
--------------------------------------------------------------------------------
/src/pages/Game.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { connect } from 'react-redux';
5 | import { start } from '../actions/start';
6 | import { restart } from '../actions/restart';
7 | import { getIntro, getCurrent, getAnswers } from '../selectors';
8 | import ButtonContainer from '../components/ButtonContainer';
9 | import ProgressBar from '../components/ProgressBar';
10 | import Code from '../components/Code';
11 | import Result from '../components/Result';
12 | import Percentage from '../components/Percentage';
13 | import Button from '../components/Button';
14 |
15 | const Intro = styled.div`
16 | border: 1px solid ${props => props.theme.border};
17 | border-radius: 4px;
18 | color: ${props => props.theme.text};
19 | margin: 2rem 0;
20 | padding: 2rem 3rem;
21 | `;
22 |
23 | const StartButtonContainer = styled.div`
24 | margin: 3rem auto 1rem;
25 | `;
26 |
27 | const Restart = styled.div`
28 | margin: 3rem 0;
29 | text-align: center;
30 | `;
31 |
32 | const GitHubButton = styled.a`
33 | background: #e9ecef;
34 | color: #495057;
35 | padding: 6px 12px;
36 | border-radius: 4px;
37 | margin: 0 8px;
38 | text-decoration: none;
39 | font-size: 0.8rem;
40 | `;
41 |
42 | const ShareContainer = styled.p`
43 | text-align: center;
44 | `;
45 |
46 | const Game = ({ intro, current, answers, style, onStart, onRestart }) => (
47 | <>
48 | {intro && (
49 |
50 |
51 | Each question contains a code snippet and four answer choices.
52 |
53 | Look carefully at the code and choose the one correct answer.
54 |
55 | After answering all 23 questions you'll be shown your results.
56 |
57 |
58 |
59 |
60 | )}
61 |
62 | {!intro && current && (
63 | <>
64 |
65 |
66 |
67 | >
68 | )}
69 |
70 | {!intro && !current && (
71 | <>
72 |
73 |
74 |
75 |
76 |
77 |
78 |
83 | Star on GitHub
84 |
85 |
86 | >
87 | )}
88 | >
89 | );
90 |
91 | Game.propTypes = {
92 | style: PropTypes.object.isRequired,
93 | onStart: PropTypes.func.isRequired,
94 | onRestart: PropTypes.func.isRequired,
95 | answers: PropTypes.array.isRequired,
96 | intro: PropTypes.bool.isRequired,
97 | current: PropTypes.object
98 | };
99 |
100 | export default connect(
101 | state => ({
102 | intro: getIntro(state),
103 | current: getCurrent(state),
104 | answers: getAnswers(state)
105 | }),
106 | {
107 | onStart: () => start(),
108 | onRestart: () => restart()
109 | }
110 | )(Game);
111 |
--------------------------------------------------------------------------------
/src/pages/Patterns.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router-dom';
3 | import PatternsList from '../components/PatternsList';
4 | import Pattern from '../components/Pattern';
5 |
6 | const Patterns = () => (
7 |
8 |
9 |
10 |
11 | );
12 |
13 | export default Patterns;
14 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | SUBMIT,
3 | TOGGLE,
4 | TOGGLE_JS,
5 | TOGGLE_MODE,
6 | START,
7 | RESTART
8 | } from '../static/constants/actions';
9 | import { randomFromRange } from '../helpers/randomFromRange';
10 |
11 | const rootReducer = (state, action) => {
12 | switch (action.type) {
13 | case SUBMIT:
14 | const { recentlyAnswered, remainingPatterns, currentIndex } = action.payload;
15 |
16 | const current = remainingPatterns.length ? remainingPatterns[currentIndex] : null;
17 |
18 | return {
19 | ...state,
20 | progress: {
21 | answers: state.progress.answers.concat(recentlyAnswered),
22 | remaining: remainingPatterns,
23 | current
24 | }
25 | };
26 | case TOGGLE:
27 | return state;
28 | case TOGGLE_JS:
29 | return { ...state, js: action.payload };
30 | case TOGGLE_MODE:
31 | return { ...state, mode: action.payload };
32 | case START:
33 | return { ...state, intro: false };
34 | case RESTART:
35 | return {
36 | ...state,
37 | progress: {
38 | answers: [],
39 | remaining: state.patterns,
40 | current: state.patterns[randomFromRange(state.patterns.length)]
41 | }
42 | };
43 | default:
44 | return state;
45 | }
46 | };
47 |
48 | export default rootReducer;
49 |
--------------------------------------------------------------------------------
/src/selectors/index.js:
--------------------------------------------------------------------------------
1 | export const getRemaining = state => state.progress.remaining;
2 |
3 | export const getPatterns = state => state.patterns;
4 |
5 | export const getMode = state => state.mode;
6 |
7 | export const getJS = state => state.js;
8 |
9 | export const getIntro = state => state.intro;
10 |
11 | export const getCurrent = state => state.progress.current;
12 |
13 | export const getAnswers = state => state.progress.answers;
14 |
--------------------------------------------------------------------------------
/src/static/constants/actions.js:
--------------------------------------------------------------------------------
1 | export const SUBMIT = 'SUBMIT';
2 | export const TOGGLE = 'TOGGLE';
3 | export const TOGGLE_JS = 'TOGGLE_JS';
4 | export const TOGGLE_MODE = 'TOGGLE_MODE';
5 | export const START = 'START';
6 | export const RESTART = 'RESTART';
7 |
--------------------------------------------------------------------------------
/src/static/constants/colors.js:
--------------------------------------------------------------------------------
1 | // common theme
2 | export const ATLANTIS = '#9ACD32';
3 | export const CRIMSON = '#E22A23';
4 | export const ALTO = '#D8D8D8';
5 |
6 | // dark themeALTO
7 | export const COAL_MINE = '#222222';
8 | export const MINE_SHAFT = '#282828';
9 | export const TUNDORA = '#484848';
10 | export const DOVE = '#707070';
11 | export const GRAY = '#888888';
12 | export const SILVER = '#C8C8C8';
13 | export const WILD_SAND = '#F5F5F5';
14 | export const WHITE = '#FFFFFF';
15 |
16 | // light theme
17 | export const PLUM = '#6F256F';
18 | export const BOUQUET = '#A568A5';
19 | export const ORCHID = '#EDB8ED';
20 | export const PRIM = '#F2E8F2';
21 |
--------------------------------------------------------------------------------
/src/static/constants/themes.js:
--------------------------------------------------------------------------------
1 | export const THEME_LIGHT = 'LIGHT';
2 | export const THEME_DARK = 'DARK';
3 |
--------------------------------------------------------------------------------
/src/static/icons/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React, { Fragment } from 'react';
3 |
4 | export const JS = (
5 |
6 |
13 |
17 |
24 |
25 | );
26 |
27 | export const SUN_OUTLINED = (
28 |
29 |
30 |
31 |
32 | );
33 |
34 | export const SUN_FILLED = (
35 |
36 |
37 |
38 |
39 | );
40 |
41 | export const ICON_FACEBOOK = (
42 |
43 | facebook
44 |
45 |
46 | );
47 |
48 | export const ICON_TWITTER = (
49 |
50 | twitter
51 |
52 |
53 | );
54 |
55 | export const ICON_LINKEDIN = (
56 |
57 | linkedin
58 |
59 |
60 |
61 |
62 | );
63 |
64 | export const ICON_REDDIT = (
65 |
66 | reddit
67 |
68 |
69 | );
70 |
71 | export const ICON_GITHUB = (
72 |
73 | github
74 |
75 |
76 | );
77 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_chainOfResponsibility.js:
--------------------------------------------------------------------------------
1 | const CHAIN_OF_RESPONSIBILITY = {
2 | id: 'chain_of_responsibility',
3 | name: 'Chain of Responsibility',
4 | type: 'behavioral',
5 | hint: 'A way of passing a request between a chain of objects',
6 | definition: `Avoid coupling the sender of a request to its receiver by giving more than one object a chance to
7 | handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.`,
8 | when: 'more than one object can handle a request and that information is known in runtime',
9 | codeES5: `function ShoppingCart() {
10 | this.products = [];
11 |
12 | this.addProduct = function(p) {
13 | this.products.push(p);
14 | };
15 | }
16 |
17 | function Discount() {
18 | this.calc = function(products) {
19 | var ndiscount = new NumberDiscount();
20 | var pdiscount = new PriceDiscount();
21 | var none = new NoneDiscount();
22 |
23 | ndiscount.setNext(pdiscount);
24 | pdiscount.setNext(none);
25 |
26 | return ndiscount.exec(products);
27 | };
28 | }
29 |
30 | function NumberDiscount() {
31 | this.next = null;
32 | this.setNext = function(fn) {
33 | this.next = fn;
34 | };
35 |
36 | this.exec = function(products) {
37 | var result = 0;
38 | if (products.length > 3) result = 0.05;
39 |
40 | return result + this.next.exec(products);
41 | };
42 | }
43 |
44 | function PriceDiscount() {
45 | this.next = null;
46 | this.setNext = function(fn) {
47 | this.next = fn;
48 | };
49 | this.exec = function(products) {
50 | var result = 0;
51 | var total = products.reduce(function(a, b) {
52 | return a + b;
53 | });
54 |
55 | if (total >= 500) result = 0.1;
56 |
57 | return result + this.next.exec(products);
58 | };
59 | }
60 |
61 | function NoneDiscount() {
62 | this.exec = function() {
63 | return 0;
64 | };
65 | }
66 |
67 | module.exports = [ShoppingCart, Discount];`,
68 | codeES6: `class ShoppingCart {
69 | constructor() {
70 | this.products = [];
71 | }
72 |
73 | addProduct(p) {
74 | this.products.push(p);
75 | }
76 | }
77 |
78 | class Discount {
79 | calc(products) {
80 | let ndiscount = new NumberDiscount();
81 | let pdiscount = new PriceDiscount();
82 | let none = new NoneDiscount();
83 | ndiscount.setNext(pdiscount);
84 | pdiscount.setNext(none);
85 |
86 | return ndiscount.exec(products);
87 | }
88 | }
89 |
90 | class NumberDiscount {
91 | constructor() {
92 | this.next = null;
93 | }
94 |
95 | setNext(fn) {
96 | this.next = fn;
97 | }
98 |
99 | exec(products) {
100 | let result = 0;
101 | if (products.length > 3) result = 0.05;
102 |
103 | return result + this.next.exec(products);
104 | }
105 | }
106 |
107 | class PriceDiscount {
108 | constructor() {
109 | this.next = null;
110 | }
111 |
112 | setNext(fn) {
113 | this.next = fn;
114 | }
115 |
116 | exec(products) {
117 | let result = 0;
118 | let total = products.reduce((a, b) => a + b);
119 |
120 | if (total >= 500) result = 0.1;
121 |
122 | return result + this.next.exec(products);
123 | }
124 | }
125 |
126 | class NoneDiscount {
127 | exec() {
128 | return 0;
129 | }
130 | }
131 |
132 | export { ShoppingCart, Discount };`
133 | };
134 |
135 | export default CHAIN_OF_RESPONSIBILITY;
136 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_command.js:
--------------------------------------------------------------------------------
1 | const COMMAND = {
2 | id: 'command',
3 | name: 'Command',
4 | type: 'behavioral',
5 | hint: 'Encapsulate a command request as an object',
6 | definition: `Encapsulate a request as an object, thereby letting you parameterize clients with different requests,
7 | queue or log requests, and support undoable operations.`,
8 | when:
9 | 'you have a queue of requests to handle or you want to log them. Also when you want to have an «undo» action',
10 | codeES5: `function Cockpit(instruction) {
11 | this.instruction = instruction;
12 | }
13 |
14 | Cockpit.prototype.execute = function() {
15 | this.instruction.execute();
16 | };
17 |
18 | function Turbine() {
19 | this.speed = 0;
20 | this.state = false;
21 | }
22 |
23 | Turbine.prototype.on = function() {
24 | this.state = true;
25 | this.speed = 100;
26 | };
27 |
28 | Turbine.prototype.off = function() {
29 | this.speed = 0;
30 | this.state = false;
31 | };
32 |
33 | Turbine.prototype.speedDown = function() {
34 | if (!this.state) return;
35 |
36 | this.speed -= 100;
37 | };
38 |
39 | Turbine.prototype.speedUp = function() {
40 | if (!this.state) return;
41 |
42 | this.speed += 100;
43 | };
44 |
45 | function OnInstruction(turbine) {
46 | this.turbine = turbine;
47 | }
48 |
49 | OnInstruction.prototype.execute = function() {
50 | this.turbine.on();
51 | };
52 |
53 | function OffInstruction(turbine) {
54 | this.turbine = turbine;
55 | }
56 |
57 | OffInstruction.prototype.execute = function() {
58 | this.turbine.off();
59 | };
60 |
61 | function SpeedUpInstruction(turbine) {
62 | this.turbine = turbine;
63 | }
64 |
65 | SpeedUpInstruction.prototype.execute = function() {
66 | this.turbine.speedUp();
67 | };
68 |
69 | function SpeedDownInstruction(turbine) {
70 | this.turbine = turbine;
71 | }
72 |
73 | SpeedDownInstruction.prototype.execute = function() {
74 | this.turbine.speedDown();
75 | };
76 |
77 | module.exports = [Cockpit, Turbine, OnInstruction, OffInstruction, SpeedUpInstruction, SpeedDownInstruction];`,
78 | codeES6: `class Cockpit {
79 | constructor(instruction) {
80 | this.instruction = instruction;
81 | }
82 |
83 | execute() {
84 | this.instruction.execute();
85 | }
86 | }
87 |
88 | class Turbine {
89 | constructor() {
90 | this.state = false;
91 | }
92 |
93 | on() {
94 | this.state = true;
95 | }
96 |
97 | off() {
98 | this.state = false;
99 | }
100 | }
101 |
102 | class OnInstruction {
103 | constructor(turbine) {
104 | this.turbine = turbine;
105 | }
106 |
107 | execute() {
108 | this.turbine.on();
109 | }
110 | }
111 |
112 | class OffInstruction {
113 | constructor(turbine) {
114 | this.turbine = turbine;
115 | }
116 |
117 | execute() {
118 | this.turbine.off();
119 | }
120 | }
121 |
122 | export { Cockpit, Turbine, OnInstruction, OffInstruction };`
123 | };
124 |
125 | export default COMMAND;
126 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_interpreter.js:
--------------------------------------------------------------------------------
1 | const INTERPRETER = {
2 | id: 'interpteter',
3 | name: 'Interpreter',
4 | type: 'behavioral',
5 | hint: 'A way to include language elements in a program',
6 | definition: `Given a language, define a representation for its grammar along with an interpreter that
7 | uses the representation to interpret sentences in the language.`,
8 | when:
9 | 'you want to interpret given language and you can represent statements as an abstract syntax trees',
10 | codeES5: `function Sum(left, right) {
11 | this.left = left;
12 | this.right = right;
13 | }
14 |
15 | Sum.prototype.pattern = function() {
16 | return this.left.pattern() + this.right.pattern();
17 | };
18 |
19 | function Min(left, right) {
20 | this.left = left;
21 | this.right = right;
22 | }
23 |
24 | Min.prototype.pattern = function() {
25 | return this.left.pattern() - this.right.pattern();
26 | };
27 |
28 | function Num(val) {
29 | this.val = val;
30 | }
31 |
32 | Num.prototype.pattern = function() {
33 | return this.val;
34 | };
35 |
36 | module.exports = [Num, Min, Sum];`,
37 | codeES6: `class Sum {
38 | constructor(left, right) {
39 | this.left = left;
40 | this.right = right;
41 | }
42 |
43 | pattern() {
44 | return this.left.pattern() + this.right.pattern();
45 | }
46 | }
47 |
48 | class Min {
49 | constructor(left, right) {
50 | this.left = left;
51 | this.right = right;
52 | }
53 |
54 | pattern() {
55 | return this.left.pattern() - this.right.pattern();
56 | }
57 | }
58 |
59 | class Num {
60 | constructor(val) {
61 | this.val = val;
62 | }
63 |
64 | pattern() {
65 | return this.val;
66 | }
67 | }
68 |
69 | export { Num, Min, Sum };`
70 | };
71 |
72 | export default INTERPRETER;
73 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_iterator.js:
--------------------------------------------------------------------------------
1 | const ITERATOR = {
2 | id: 'iterator',
3 | name: 'Iterator',
4 | type: 'behavioral',
5 | hint: 'Sequentially access the elements of a collection',
6 | definition: `Provide a way to access the elements of an aggregate object sequentially
7 | without exposing its underlying representation.`,
8 | when: "you want to access object's content without knowing how it is internally represented",
9 | codeES5: `function Pattern(el) {
10 | this.index = 0;
11 | this.elements = el;
12 | }
13 |
14 | Pattern.prototype = {
15 | next: function() {
16 | return this.elements[this.index++];
17 | },
18 | hasNext: function() {
19 | return this.index < this.elements.length;
20 | }
21 | };
22 |
23 | module.exports = Pattern;`,
24 | codeES6: `class Pattern {
25 | constructor(el) {
26 | this.index = 0;
27 | this.elements = el;
28 | }
29 |
30 | next() {
31 | return this.elements[this.index++];
32 | }
33 |
34 | hasNext() {
35 | return this.index < this.elements.length;
36 | }
37 | }
38 |
39 | export default Pattern;`
40 | };
41 |
42 | export default ITERATOR;
43 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_mediator.js:
--------------------------------------------------------------------------------
1 | const MEDIATOR = {
2 | id: 'mediator',
3 | name: 'Mediator',
4 | type: 'behavioral',
5 | hint: 'Defines simplified communication between classes',
6 | definition: `Define an object that encapsulates how a set of objects interact.
7 | Mediator promotes loose coupling by keeping objects from referring to each other explicitly,
8 | and it lets you vary their interaction independently.`,
9 | when: 'a set of objects communicate in structured but complex ways',
10 | codeES5: `function TrafficTower() {
11 | this.airplanes = [];
12 | }
13 |
14 | TrafficTower.prototype.requestPositions = function() {
15 | return this.airplanes.map(function(airplane) {
16 | return airplane.position;
17 | });
18 | };
19 |
20 | function Airplane(position, trafficTower) {
21 | this.position = position;
22 | this.trafficTower = trafficTower;
23 | this.trafficTower.airplanes.push(this);
24 | }
25 |
26 | Airplane.prototype.requestPositions = function() {
27 | return this.trafficTower.requestPositions();
28 | };
29 |
30 | module.exports = [TrafficTower, Airplane];`,
31 | codeES6: `class TrafficTower {
32 | constructor() {
33 | this.airplanes = [];
34 | }
35 |
36 | requestPositions() {
37 | return this.airplanes.map(airplane => {
38 | return airplane.position;
39 | });
40 | }
41 | }
42 |
43 | class Airplane {
44 | constructor(position, trafficTower) {
45 | this.position = position;
46 | this.trafficTower = trafficTower;
47 | this.trafficTower.airplanes.push(this);
48 | }
49 |
50 | requestPositions() {
51 | return this.trafficTower.requestPositions();
52 | }
53 | }
54 |
55 | export { TrafficTower, Airplane };`
56 | };
57 |
58 | export default MEDIATOR;
59 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_memento.js:
--------------------------------------------------------------------------------
1 | const MEMENTO = {
2 | id: 'memento',
3 | name: 'Memento',
4 | type: 'behavioral',
5 | hint: "Capture and restore an object's internal state",
6 | definition: `Without violating encapsulation, capture and externalize an object's internal state
7 | so that the object can be restored to this state later.`,
8 | when: 'you need to take a snapshot of an object',
9 | codeES5: `function Pattern(value) {
10 | this.value = value;
11 | }
12 |
13 | var originator = {
14 | store: function(val) {
15 | return new Pattern(val);
16 | },
17 | restore: function(pattern) {
18 | return pattern.value;
19 | }
20 | };
21 |
22 | function Caretaker() {
23 | this.values = [];
24 | }
25 |
26 | Caretaker.prototype.addPattern = function(pattern) {
27 | this.values.push(pattern);
28 | };
29 |
30 | Caretaker.prototype.getPattern = function(index) {
31 | return this.values[index];
32 | };
33 |
34 | module.exports = [originator, Caretaker];`,
35 | codeES6: `class Pattern {
36 | constructor(value) {
37 | this.value = value;
38 | }
39 | }
40 |
41 | const originator = {
42 | store: function(val) {
43 | return new Pattern(val);
44 | },
45 | restore: function(pattern) {
46 | return pattern.value;
47 | }
48 | };
49 |
50 | class Caretaker {
51 | constructor() {
52 | this.values = [];
53 | }
54 |
55 | addPattern(pattern) {
56 | this.values.push(pattern);
57 | }
58 |
59 | getPattern(index) {
60 | return this.values[index];
61 | }
62 | }
63 |
64 | export { originator, Caretaker };`
65 | };
66 |
67 | export default MEMENTO;
68 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_observer.js:
--------------------------------------------------------------------------------
1 | const OBSERVER = {
2 | id: 'observer',
3 | name: 'Observer',
4 | type: 'behavioral',
5 | hint: 'A way of notifying change to a number of classes',
6 | definition: `Define a one-to-many dependency between objects so that when one object changes state,
7 | all its dependents are notified and updated automatically.`,
8 | when: 'a change to one object requires changing others',
9 | codeES5: `function Product() {
10 | this.price = 0;
11 | this.actions = [];
12 | }
13 |
14 | Product.prototype.setBasePrice = function(val) {
15 | this.price = val;
16 | this.notifyAll();
17 | };
18 |
19 | Product.prototype.register = function(observer) {
20 | this.actions.push(observer);
21 | };
22 |
23 | Product.prototype.unregister = function(observer) {
24 | this.actions.remove.filter(function(el) {
25 | return el !== observer;
26 | });
27 | };
28 |
29 | Product.prototype.notifyAll = function() {
30 | return this.actions.forEach(
31 | function(el) {
32 | el.update(this);
33 | }.bind(this)
34 | );
35 | };
36 |
37 | var fees = {
38 | update: function(product) {
39 | product.price = product.price * 1.2;
40 | }
41 | };
42 |
43 | var profit = {
44 | update: function(product) {
45 | product.price = product.price * 2;
46 | }
47 | };
48 |
49 | module.exports = [Product, fees, profit];`,
50 | codeES6: `class Product {
51 | constructor() {
52 | this.price = 0;
53 | this.actions = [];
54 | }
55 |
56 | setBasePrice(val) {
57 | this.price = val;
58 | this.notifyAll();
59 | }
60 |
61 | register(observer) {
62 | this.actions.push(observer);
63 | }
64 |
65 | unregister(observer) {
66 | this.actions.remove.filter(function(el) {
67 | return el !== observer;
68 | });
69 | }
70 |
71 | notifyAll() {
72 | return this.actions.forEach(
73 | function(el) {
74 | el.update(this);
75 | }.bind(this)
76 | );
77 | }
78 | }
79 |
80 | class fees {
81 | update(product) {
82 | product.price = product.price * 1.2;
83 | }
84 | }
85 |
86 | class profit {
87 | update(product) {
88 | product.price = product.price * 2;
89 | }
90 | }
91 |
92 | export { Product, fees, profit };`
93 | };
94 |
95 | export default OBSERVER;
96 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_state.js:
--------------------------------------------------------------------------------
1 | const STATE = {
2 | id: 'state',
3 | name: 'State',
4 | type: 'behavioral',
5 | hint: "Alter an object's behavior when its state changes",
6 | definition: `Allow an object to alter its behavior when its internal state changes.
7 | The object will appear to change its class.`,
8 | when: `the object's behaviour depends on its state and its behaviour changes in run-time depends on that state`,
9 | codeES5: `function Order() {
10 | this.pattern = new WaitingForPayment();
11 |
12 | this.nextPattern = function() {
13 | this.pattern = this.pattern.next();
14 | };
15 | }
16 |
17 | function WaitingForPayment() {
18 | this.name = 'waitingForPayment';
19 | this.next = function() {
20 | return new Shipping();
21 | };
22 | }
23 |
24 | function Shipping() {
25 | this.name = 'shipping';
26 | this.next = function() {
27 | return new Delivered();
28 | };
29 | }
30 |
31 | function Delivered() {
32 | this.name = 'delivered';
33 | this.next = function() {
34 | return this;
35 | };
36 | }
37 |
38 | module.exports = Order;`,
39 | codeES6: `class OrderStatus {
40 | constructor(name, nextStatus) {
41 | this.name = name;
42 | this.nextStatus = nextStatus;
43 | }
44 |
45 | next() {
46 | return new this.nextStatus();
47 | }
48 | }
49 |
50 | class WaitingForPayment extends OrderStatus {
51 | constructor() {
52 | super('waitingForPayment', Shipping);
53 | }
54 | }
55 |
56 | class Shipping extends OrderStatus {
57 | constructor() {
58 | super('shipping', Delivered);
59 | }
60 | }
61 |
62 | class Delivered extends OrderStatus {
63 | constructor() {
64 | super('delivered', Delivered);
65 | }
66 | }
67 |
68 | class Order {
69 | constructor() {
70 | this.pattern = new WaitingForPayment();
71 | }
72 |
73 | nextPattern() {
74 | this.pattern = this.pattern.next();
75 | }
76 | }
77 |
78 | export default Order;`
79 | };
80 |
81 | export default STATE;
82 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_strategy.js:
--------------------------------------------------------------------------------
1 | const STRATEGY = {
2 | id: 'strategy',
3 | name: 'Strategy',
4 | type: 'behavioral',
5 | hint: 'Encapsulates an algorithm inside a class',
6 | definition: `Define a family of algorithms, encapsulate each one, and make them interchangeable.
7 | Strategy lets the algorithm vary independently from clients that use it.`,
8 | when: `you have many classes that differ in their behaviour.
9 | Strategies allow to configure a class with one of many behaviours`,
10 | codeES5: `function ShoppingCart(discount) {
11 | this.discount = discount;
12 | this.amount = 0;
13 | }
14 |
15 | ShoppingCart.prototype.setAmount = function(amount) {
16 | this.amount = amount;
17 | };
18 |
19 | ShoppingCart.prototype.checkout = function() {
20 | return this.discount(this.amount);
21 | };
22 |
23 | function guestPattern(amount) {
24 | return amount;
25 | }
26 |
27 | function regularPattern(amount) {
28 | return amount * 0.9;
29 | }
30 |
31 | function premiumPattern(amount) {
32 | return amount * 0.8;
33 | }
34 |
35 | module.exports = [ShoppingCart, guestPattern, regularPattern, premiumPattern];`,
36 | codeES6: `class ShoppingCart {
37 | constructor(discount) {
38 | this.discount = discount;
39 | this.amount = 0;
40 | }
41 |
42 | checkout() {
43 | return this.discount(this.amount);
44 | }
45 |
46 | setAmount(amount) {
47 | this.amount = amount;
48 | }
49 | }
50 |
51 | function guestPattern(amount) {
52 | return amount;
53 | }
54 |
55 | function regularPattern(amount) {
56 | return amount * 0.9;
57 | }
58 |
59 | function premiumPattern(amount) {
60 | return amount * 0.8;
61 | }
62 |
63 | export { ShoppingCart, guestPattern, regularPattern, premiumPattern };`
64 | };
65 |
66 | export default STRATEGY;
67 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_template.js:
--------------------------------------------------------------------------------
1 | const TEMPLATE = {
2 | id: 'template',
3 | name: 'Template',
4 | type: 'behavioral',
5 | hint: 'Defer the exact steps of an algorithm to a subclass',
6 | definition: `Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
7 | Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.`,
8 | when: `you have to define steps of the algorithm once and let subclasses to implement its behaviour`,
9 | codeES5: `function Tax() {}
10 | Tax.prototype.calc = function(value) {
11 | if (value >= 1000) value = this.overThousand(value);
12 |
13 | return this.complementaryFee(value);
14 | };
15 |
16 | Tax.prototype.complementaryFee = function(value) {
17 | return value + 10;
18 | };
19 |
20 | function Tax1() {}
21 | Tax1.prototype = Object.create(Tax.prototype);
22 |
23 | Tax1.prototype.overThousand = function(value) {
24 | return value * 1.1;
25 | };
26 |
27 | function Tax2() {}
28 | Tax2.prototype = Object.create(Tax.prototype);
29 |
30 | Tax2.prototype.overThousand = function(value) {
31 | return value * 1.2;
32 | };
33 |
34 | module.exports = [Tax1, Tax2];`,
35 | codeES6: `class Tax {
36 | calc(value) {
37 | if (value >= 1000) value = this.overThousand(value);
38 |
39 | return this.complementaryFee(value);
40 | }
41 |
42 | complementaryFee(value) {
43 | return value + 10;
44 | }
45 | }
46 |
47 | class Tax1 extends Tax {
48 | constructor() {
49 | super();
50 | }
51 |
52 | overThousand(value) {
53 | return value * 1.1;
54 | }
55 | }
56 |
57 | class Tax2 extends Tax {
58 | constructor() {
59 | super();
60 | }
61 |
62 | overThousand(value) {
63 | return value * 1.2;
64 | }
65 | }
66 |
67 | export { Tax1, Tax2 };`
68 | };
69 |
70 | export default TEMPLATE;
71 |
--------------------------------------------------------------------------------
/src/static/patterns/behavioral_visitor.js:
--------------------------------------------------------------------------------
1 | const VISITOR = {
2 | id: 'visitor',
3 | name: 'Visitor',
4 | type: 'behavioral',
5 | hint: 'Defines a new operation to a class without change',
6 | definition: `Represent an operation to be performed on the elements of an object structure.
7 | Visitor lets you define a new operation without changing the classes of the elements on which it operates.`,
8 | when: `an object structure includes many classes and you want to perform an operations
9 | on the elements of that structure that depend on their classes`,
10 | codeES5: `function bonusPattern(employee) {
11 | if (employee instanceof Manager) employee.bonus = employee.salary * 2;
12 | if (employee instanceof Developer) employee.bonus = employee.salary;
13 | }
14 |
15 | function Employee() {
16 | this.bonus = 0;
17 | }
18 |
19 | Employee.prototype.accept = function(item) {
20 | item(this);
21 | };
22 |
23 | function Manager(salary) {
24 | this.salary = salary;
25 | }
26 |
27 | Manager.prototype = Object.create(Employee.prototype);
28 |
29 | function Developer(salary) {
30 | this.salary = salary;
31 | }
32 |
33 | Developer.prototype = Object.create(Employee.prototype);
34 |
35 | module.exports = [Developer, Manager, bonusPattern];`,
36 | codeES6: `function bonusPattern(employee) {
37 | if (employee instanceof Manager) employee.bonus = employee.salary * 2;
38 | if (employee instanceof Developer) employee.bonus = employee.salary;
39 | }
40 |
41 | class Employee {
42 | constructor(salary) {
43 | this.bonus = 0;
44 | this.salary = salary;
45 | }
46 |
47 | accept(item) {
48 | item(this);
49 | }
50 | }
51 |
52 | class Manager extends Employee {
53 | constructor(salary) {
54 | super(salary);
55 | }
56 | }
57 |
58 | class Developer extends Employee {
59 | constructor(salary) {
60 | super(salary);
61 | }
62 | }
63 |
64 | export { Developer, Manager, bonusPattern };`
65 | };
66 |
67 | export default VISITOR;
68 |
--------------------------------------------------------------------------------
/src/static/patterns/creational_abstractFactory.js:
--------------------------------------------------------------------------------
1 | const ABSTRACT_FACTORY = {
2 | id: 'abstract_factory',
3 | name: 'Abstract Factory',
4 | type: 'creational',
5 | hint: 'Creates an instance of several families of classes',
6 | definition: `Provide an interface for creating families of related or dependent objects
7 | without specifying their concrete classes.`,
8 | when: 'system should be independent of how what it is producing is structured or represented',
9 | codeES5: `function droidProducer(kind) {
10 | if (kind === 'battle') return battleDroidPattern;
11 | return pilotDroidPattern;
12 | }
13 |
14 | function battleDroidPattern() {
15 | return new B1();
16 | }
17 |
18 | function pilotDroidPattern() {
19 | return new Rx24();
20 | }
21 |
22 | function B1() {}
23 | B1.prototype.info = function() {
24 | return 'B1, Battle Droid';
25 | };
26 |
27 | function Rx24() {}
28 | Rx24.prototype.info = function() {
29 | return 'Rx24, Pilot Droid';
30 | };
31 |
32 | module.exports = droidProducer;`,
33 | codeES6: `function droidProducer(kind) {
34 | if (kind === 'battle') return battleDroidPattern;
35 | return pilotDroidPattern;
36 | }
37 |
38 | function battleDroidPattern() {
39 | return new B1();
40 | }
41 |
42 | function pilotDroidPattern() {
43 | return new Rx24();
44 | }
45 |
46 | class B1 {
47 | info() {
48 | return 'B1, Battle Droid';
49 | }
50 | }
51 |
52 | class Rx24 {
53 | info() {
54 | return 'Rx24, Pilot Droid';
55 | }
56 | }
57 |
58 | export default droidProducer;`
59 | };
60 |
61 | export default ABSTRACT_FACTORY;
62 |
--------------------------------------------------------------------------------
/src/static/patterns/creational_builder.js:
--------------------------------------------------------------------------------
1 | const BUILDER = {
2 | id: 'builder',
3 | name: 'Builder',
4 | type: 'creational',
5 | hint: 'Separates object construction from its representation',
6 | definition: `Separate the construction of a complex object from its representation
7 | so that the same construction process can create different representations.`,
8 | when: 'algorithm of creation is independent of the parts of the object',
9 | codeES5: `function Request() {
10 | this.url = '';
11 | this.method = '';
12 | this.payload = {};
13 | }
14 |
15 | function RequestPattern() {
16 | this.request = new Request();
17 |
18 | this.forUrl = function(url) {
19 | this.request.url = url;
20 | return this;
21 | };
22 |
23 | this.useMethod = function(method) {
24 | this.request.method = method;
25 | return this;
26 | };
27 |
28 | this.payload = function(payload) {
29 | this.request.payload = payload;
30 | return this;
31 | };
32 |
33 | this.build = function() {
34 | return this.request;
35 | };
36 | }
37 |
38 | module.exports = RequestPattern;`,
39 | codeES6: `class Request {
40 | constructor() {
41 | this.url = '';
42 | this.method = '';
43 | this.payload = {};
44 | }
45 | }
46 |
47 | class RequestPattern {
48 | constructor() {
49 | this.request = new Request();
50 | }
51 |
52 | forUrl(url) {
53 | this.request.url = url;
54 | return this;
55 | }
56 |
57 | useMethod(method) {
58 | this.request.method = method;
59 | return this;
60 | }
61 |
62 | payload(payload) {
63 | this.request.payload = payload;
64 | return this;
65 | }
66 |
67 | build() {
68 | return this.request;
69 | }
70 | }
71 |
72 | export default RequestPattern;`
73 | };
74 |
75 | export default BUILDER;
76 |
--------------------------------------------------------------------------------
/src/static/patterns/creational_factory.js:
--------------------------------------------------------------------------------
1 | const FACTORY = {
2 | id: 'factory',
3 | name: 'Factory',
4 | type: 'creational',
5 | hint: 'Creates an instance of several derived classes',
6 | definition: `Define an interface for creating an object, but let subclasses decide
7 | which class to instantiate. Factory Method lets a class defer instantiation to subclasses.`,
8 | when: `a class wants its subclasses to decide which object to create`,
9 | codeES5: `function teslaPattern(type) {
10 | if (type === 'ModelX') return new Tesla(type, 108000, 300);
11 | if (type === 'ModelS') return new Tesla(type, 111000, 320);
12 | }
13 |
14 | function Tesla(model, price, maxSpeed) {
15 | this.model = model;
16 | this.price = price;
17 | this.maxSpeed = maxSpeed;
18 | }
19 |
20 | module.exports = teslaPattern;`,
21 | codeES6: `class TeslaPattern {
22 | create(type) {
23 | if (type === 'ModelX') return new Tesla(type, 108000, 300);
24 | if (type === 'ModelS') return new Tesla(type, 111000, 320);
25 | }
26 | }
27 |
28 | class Tesla {
29 | constructor(model, price, maxSpeed) {
30 | this.model = model;
31 | this.price = price;
32 | this.maxSpeed = maxSpeed;
33 | }
34 | }
35 |
36 | export default TeslaPattern;`
37 | };
38 |
39 | export default FACTORY;
40 |
--------------------------------------------------------------------------------
/src/static/patterns/creational_prototype.js:
--------------------------------------------------------------------------------
1 | const PROTOTYPE = {
2 | id: 'prototype',
3 | name: 'Prototype',
4 | type: 'creational',
5 | hint: 'A fully initialized instance to be copied or cloned',
6 | definition: `Specify the kind of objects to create using a prototypical instance,
7 | and create new objects by copying this prototype.`,
8 | when: 'classes to instantiate are available only in runtime',
9 | codeES5: `function Sheep(name, weight) {
10 | this.name = name;
11 | this.weight = weight;
12 | }
13 |
14 | Sheep.prototype.clone = function() {
15 | return new Sheep(this.name, this.weight);
16 | };
17 |
18 | module.exports = Sheep;`,
19 | codeES6: `class Sheep {
20 | constructor(name, weight) {
21 | this.name = name;
22 | this.weight = weight;
23 | }
24 |
25 | clone() {
26 | return new Sheep(this.name, this.weight);
27 | }
28 | }
29 |
30 | export default Sheep;`
31 | };
32 |
33 | export default PROTOTYPE;
34 |
--------------------------------------------------------------------------------
/src/static/patterns/creational_singleton.js:
--------------------------------------------------------------------------------
1 | const SINGLETON = {
2 | id: 'singleton',
3 | name: 'Singleton',
4 | type: 'creational',
5 | hint: 'A class of which only a single instance can exist',
6 | definition: 'Ensure a class has only one instance and provide a global point of access to it.',
7 | when: 'there must by only one instance of a class',
8 | codeES5: `function Person() {
9 | if (typeof Person.instance === 'object') return Person.instance;
10 |
11 | Person.instance = this;
12 |
13 | return this;
14 | }
15 |
16 | module.exports = Person;`,
17 | codeES6: `class Person {
18 | constructor() {
19 | if (typeof Person.instance === 'object') {
20 | return Person.instance;
21 | }
22 |
23 | Person.instance = this;
24 |
25 | return this;
26 | }
27 | }
28 |
29 | export default Person;`
30 | };
31 |
32 | export default SINGLETON;
33 |
--------------------------------------------------------------------------------
/src/static/patterns/index.js:
--------------------------------------------------------------------------------
1 | import ABSTRACT_FACTORY from './creational_abstractFactory';
2 | import BUILDER from './creational_builder';
3 | import FACTORY from './creational_factory';
4 | import PROTOTYPE from './creational_prototype';
5 | import SINGLETON from './creational_singleton';
6 | import ADAPTER from './structural_adapter';
7 | import BRIDGE from './structural_bridge';
8 | import COMPOSITE from './structural_composite';
9 | import DECORATOR from './structural_decorator';
10 | import FACADE from './structural_facade';
11 | import FLYWEIGHT from './structural_flyweight';
12 | import PROXY from './structural_proxy';
13 | import CHAIN_OF_RESPONSIBILITY from './behavioral_chainOfResponsibility';
14 | import COMMAND from './behavioral_command';
15 | import INTERPRETER from './behavioral_interpreter';
16 | import ITERATOR from './behavioral_iterator';
17 | import MEDIATOR from './behavioral_mediator';
18 | import MEMENTO from './behavioral_memento';
19 | import OBSERVER from './behavioral_observer';
20 | import STATE from './behavioral_state';
21 | import STRATEGY from './behavioral_strategy';
22 | import TEMPLATE from './behavioral_template';
23 | import VISITOR from './behavioral_visitor';
24 |
25 | export const patterns = [
26 | ABSTRACT_FACTORY,
27 | BUILDER,
28 | FACTORY,
29 | PROTOTYPE,
30 | SINGLETON,
31 |
32 | ADAPTER,
33 | BRIDGE,
34 | COMPOSITE,
35 | DECORATOR,
36 | FACADE,
37 | FLYWEIGHT,
38 | PROXY,
39 |
40 | CHAIN_OF_RESPONSIBILITY,
41 | COMMAND,
42 | INTERPRETER,
43 | ITERATOR,
44 | MEDIATOR,
45 | MEMENTO,
46 | OBSERVER,
47 | STATE,
48 | STRATEGY,
49 | TEMPLATE,
50 | VISITOR
51 | ];
52 |
53 | const mixed = [...patterns];
54 | for (let i = mixed.length - 1; i > 0; i--) {
55 | const rand = Math.floor(Math.random() * (i + 1));
56 | [mixed[i], mixed[rand]] = [mixed[rand], mixed[i]];
57 | }
58 |
59 | export default mixed;
60 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_adapter.js:
--------------------------------------------------------------------------------
1 | const ADAPTER = {
2 | id: 'adapter',
3 | name: 'Adapter',
4 | type: 'structural',
5 | hint: `Match interfaces of different classes`,
6 | definition: `Convert the interface of a class into another interface clients expect.
7 | Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.`,
8 | when: `you want to use existing class but its interface does not match the one you need`,
9 | codeES5: `function Soldier(lvl) {
10 | this.lvl = lvl;
11 | }
12 |
13 | Soldier.prototype.attack = function() {
14 | return this.lvl * 1;
15 | };
16 |
17 | function Jedi(lvl) {
18 | this.lvl = lvl;
19 | }
20 |
21 | Jedi.prototype.attackWithSaber = function() {
22 | return this.lvl * 100;
23 | };
24 |
25 | function JediPattern(jedi) {
26 | this.jedi = jedi;
27 | }
28 |
29 | JediPattern.prototype.attack = function() {
30 | return this.jedi.attackWithSaber();
31 | };
32 |
33 | module.exports = [Soldier, Jedi, JediPattern];`,
34 | codeES6: `class Soldier {
35 | constructor(level) {
36 | this.level = level;
37 | }
38 |
39 | attack() {
40 | return this.level * 1;
41 | }
42 | }
43 |
44 | class Jedi {
45 | constructor(level) {
46 | this.level = level;
47 | }
48 |
49 | attackWithSaber() {
50 | return this.level * 100;
51 | }
52 | }
53 |
54 | class JediPattern {
55 | constructor(jedi) {
56 | this.jedi = jedi;
57 | }
58 |
59 | attack() {
60 | return this.jedi.attackWithSaber();
61 | }
62 | }
63 |
64 | export { Soldier, Jedi, JediPattern };`
65 | };
66 |
67 | export default ADAPTER;
68 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_bridge.js:
--------------------------------------------------------------------------------
1 | const BRIDGE = {
2 | id: 'bridge',
3 | name: 'Bridge',
4 | type: 'structural',
5 | hint: 'Separates an object’s interface from its implementation',
6 | definition: `Decouple an abstraction from its implementation so that the two can vary independently.`,
7 | when: `you want to avoid binding between abstraction and its implementation if, for example,
8 | each of them must be selected in runtime`,
9 | codeES5: `function EpsonPrinter(ink) {
10 | this.ink = ink();
11 | }
12 |
13 | EpsonPrinter.prototype.print = function() {
14 | return 'Printer: Epson, Ink: ' + this.ink;
15 | };
16 |
17 | function HPprinter(ink) {
18 | this.ink = ink();
19 | }
20 |
21 | HPprinter.prototype.print = function() {
22 | return 'Printer: HP, Ink: ' + this.ink;
23 | };
24 |
25 | function acrylicInk() {
26 | return 'acrylic-based';
27 | }
28 |
29 | function alcoholInk() {
30 | return 'alcohol-based';
31 | }
32 |
33 | module.exports = [EpsonPrinter, HPprinter, acrylicInk, alcoholInk];`,
34 | codeES6: `class Printer {
35 | constructor(ink) {
36 | this.ink = ink;
37 | }
38 | }
39 |
40 | class EpsonPrinter extends Printer {
41 | constructor(ink) {
42 | super(ink);
43 | }
44 |
45 | print() {
46 | return 'Printer: Epson, Ink: ' + this.ink.get();
47 | }
48 | }
49 |
50 | class HPprinter extends Printer {
51 | constructor(ink) {
52 | super(ink);
53 | }
54 |
55 | print() {
56 | return 'Printer: HP, Ink: ' + this.ink.get();
57 | }
58 | }
59 |
60 | class Ink {
61 | constructor(type) {
62 | this.type = type;
63 | }
64 |
65 | get() {
66 | return this.type;
67 | }
68 | }
69 |
70 | class AcrylicInk extends Ink {
71 | constructor() {
72 | super('acrylic-based');
73 | }
74 | }
75 |
76 | class AlcoholInk extends Ink {
77 | constructor() {
78 | super('alcohol-based');
79 | }
80 | }
81 |
82 | export { EpsonPrinter, HPprinter, AcrylicInk, AlcoholInk };`
83 | };
84 |
85 | export default BRIDGE;
86 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_composite.js:
--------------------------------------------------------------------------------
1 | const COMPOSITE = {
2 | id: 'composite',
3 | name: 'Composite',
4 | type: 'structural',
5 | hint: 'A tree structure of simple and composite objects',
6 | definition: `Compose objects into tree structures to represent part-whole hierarchies.
7 | Composite lets clients treat individual objects and compositions of objects uniformly.`,
8 | when: `you want to represent hierarchies of objects`,
9 | codeES5: `function EquipmentPattern(name) {
10 | this.equipments = [];
11 | this.name = name;
12 | }
13 |
14 | EquipmentPattern.prototype.add = function(equipment) {
15 | this.equipments.push(equipment);
16 | };
17 |
18 | EquipmentPattern.prototype.getPrice = function() {
19 | return this.equipments
20 | .map(function(equipment) {
21 | return equipment.getPrice();
22 | })
23 | .reduce(function(a, b) {
24 | return a + b;
25 | });
26 | };
27 |
28 | function Equipment() {}
29 |
30 | Equipment.prototype.getPrice = function() {
31 | return this.price;
32 | };
33 |
34 | // -- leafs
35 | function FloppyDisk() {
36 | this.name = 'Floppy Disk';
37 | this.price = 70;
38 | }
39 | FloppyDisk.prototype = Object.create(Equipment.prototype);
40 |
41 | function HardDrive() {
42 | this.name = 'Hard Drive';
43 | this.price = 250;
44 | }
45 | HardDrive.prototype = Object.create(Equipment.prototype);
46 |
47 | function Memory() {
48 | this.name = '8gb memomry';
49 | this.price = 280;
50 | }
51 | Memory.prototype = Object.create(Equipment.prototype);
52 |
53 | module.exports = [EquipmentPattern, FloppyDisk, HardDrive, Memory];`,
54 | codeES6: `//Equipment
55 | class Equipment {
56 | getPrice() {
57 | return this.price || 0;
58 | }
59 |
60 | getName() {
61 | return this.name;
62 | }
63 |
64 | setName(name) {
65 | this.name = name;
66 | }
67 | }
68 |
69 | class Pattern extends Equipment {
70 | constructor() {
71 | super();
72 | this.equipments = [];
73 | }
74 |
75 | add(equipment) {
76 | this.equipments.push(equipment);
77 | }
78 |
79 | getPrice() {
80 | return this.equipments
81 | .map(equipment => {
82 | return equipment.getPrice();
83 | })
84 | .reduce((a, b) => {
85 | return a + b;
86 | });
87 | }
88 | }
89 |
90 | class Cabbinet extends Pattern {
91 | constructor() {
92 | super();
93 | this.setName('cabbinet');
94 | }
95 | }
96 |
97 | // --- leafs ---
98 | class FloppyDisk extends Equipment {
99 | constructor() {
100 | super();
101 | this.setName('Floppy Disk');
102 | this.price = 70;
103 | }
104 | }
105 |
106 | class HardDrive extends Equipment {
107 | constructor() {
108 | super();
109 | this.setName('Hard Drive');
110 | this.price = 250;
111 | }
112 | }
113 |
114 | class Memory extends Equipment {
115 | constructor() {
116 | super();
117 | this.setName('Memory');
118 | this.price = 280;
119 | }
120 | }
121 |
122 | export { Cabbinet, FloppyDisk, HardDrive, Memory };`
123 | };
124 |
125 | export default COMPOSITE;
126 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_decorator.js:
--------------------------------------------------------------------------------
1 | const DECORATOR = {
2 | id: 'decorator',
3 | name: 'Decorator',
4 | type: 'structural',
5 | hint: 'Add responsibilities to objects dynamically',
6 | definition: `Attach additional responsibilities to an object dynamically.
7 | Decorators provide a flexible alternative to subclassing for extending functionality.`,
8 | when: `you want to add extensions to an object in runtime without affecting other objects`,
9 | codeES5: `function Pasta() {
10 | this.price = 0;
11 | }
12 |
13 | Pasta.prototype.getPrice = function() {
14 | return this.price;
15 | };
16 |
17 | function Penne() {
18 | this.price = 8;
19 | }
20 |
21 | Penne.prototype = Object.create(Pasta.prototype);
22 |
23 | function SaucePattern(pasta) {
24 | this.pasta = pasta;
25 | }
26 |
27 | SaucePattern.prototype.getPrice = function() {
28 | return this.pasta.getPrice() + 5;
29 | };
30 |
31 | function CheesePattern(pasta) {
32 | this.pasta = pasta;
33 | }
34 |
35 | CheesePattern.prototype.getPrice = function() {
36 | return this.pasta.getPrice() + 3;
37 | };
38 |
39 | module.exports = [Penne, SaucePattern, CheesePattern];`,
40 | codeES6: `class Pasta {
41 | constructor() {
42 | this.price = 0;
43 | }
44 |
45 | getPrice() {
46 | return this.price;
47 | }
48 | }
49 |
50 | class Penne extends Pasta {
51 | constructor() {
52 | super();
53 | this.price = 8;
54 | }
55 | }
56 |
57 | class PastaPattern extends Pasta {
58 | constructor(pasta) {
59 | super();
60 | this.pasta = pasta;
61 | }
62 |
63 | getPrice() {
64 | return this.pasta.getPrice();
65 | }
66 | }
67 |
68 | class SaucePattern extends PastaPattern {
69 | constructor(pasta) {
70 | super(pasta);
71 | }
72 |
73 | getPrice() {
74 | return super.getPrice() + 5;
75 | }
76 | }
77 |
78 | class CheesePattern extends PastaPattern {
79 | constructor(pasta) {
80 | super(pasta);
81 | }
82 |
83 | getPrice() {
84 | return super.getPrice() + 3;
85 | }
86 | }
87 |
88 | export { Penne, SaucePattern, CheesePattern };`
89 | };
90 |
91 | export default DECORATOR;
92 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_facade.js:
--------------------------------------------------------------------------------
1 | const FACADE = {
2 | id: 'facade',
3 | name: 'Facade',
4 | type: 'structural',
5 | hint: 'A single class that represents an entire subsystem',
6 | definition: `Provide a unified interface to a set of interfaces in a subsystem.
7 | Facade defines a higher-level interface that makes the subsystem easier to use.`,
8 | when: `you want to provide a simple interface to a complex subsystem`,
9 | codeES5: `var shopPattern = {
10 | calc: function(price) {
11 | price = discount(price);
12 | price = fees(price);
13 | price += shipping();
14 |
15 | return price;
16 | }
17 | };
18 |
19 | function discount(value) {
20 | return value * 0.9;
21 | }
22 |
23 | function shipping() {
24 | return 5;
25 | }
26 |
27 | function fees(value) {
28 | return value * 1.05;
29 | }
30 |
31 | module.exports = shopPattern;`,
32 | codeES6: `class ShopPattern {
33 | constructor() {
34 | this.discount = new Discount();
35 | this.shipping = new Shipping();
36 | this.fees = new Fees();
37 | }
38 |
39 | calc(price) {
40 | price = this.discount.calc(price);
41 | price = this.fees.calc(price);
42 | price += this.shipping.calc();
43 |
44 | return price;
45 | }
46 | }
47 |
48 | class Discount {
49 | calc(value) {
50 | return value * 0.9;
51 | }
52 | }
53 |
54 | class Shipping {
55 | calc() {
56 | return 5;
57 | }
58 | }
59 |
60 | class Fees {
61 | calc(value) {
62 | return value * 1.05;
63 | }
64 | }
65 |
66 | export default ShopPattern;`
67 | };
68 |
69 | export default FACADE;
70 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_flyweight.js:
--------------------------------------------------------------------------------
1 | const FLYWEIGHT = {
2 | id: 'flyweight',
3 | name: 'Flyweight',
4 | type: 'structural',
5 | hint: 'A fine-grained instance used for efficient sharing',
6 | definition: `Use sharing to support large numbers of fine-grained objects efficiently.`,
7 | when: `an application uses a lot of small objects and their storing is expensive or their identity is not important`,
8 | codeES5: `function Color(name) {
9 | this.name = name;
10 | }
11 |
12 | var colorCreator = {
13 | colors: {},
14 | create: function(name) {
15 | var color = this.colors[name];
16 | if (color) return color;
17 |
18 | this.colors[name] = new Color(name);
19 |
20 | return this.colors[name];
21 | }
22 | };
23 |
24 | module.exports = colorCreator;`,
25 | codeES6: `class Color {
26 | constructor(name) {
27 | this.name = name;
28 | }
29 | }
30 |
31 | class colorCreator {
32 | constructor() {
33 | this.colors = {};
34 | }
35 |
36 | create(name) {
37 | let color = this.colors[name];
38 | if (color) return color;
39 |
40 | this.colors[name] = new Color(name);
41 |
42 | return this.colors[name];
43 | }
44 | }
45 |
46 | export { colorCreator };`
47 | };
48 |
49 | export default FLYWEIGHT;
50 |
--------------------------------------------------------------------------------
/src/static/patterns/structural_proxy.js:
--------------------------------------------------------------------------------
1 | const PROXY = {
2 | id: 'proxy',
3 | name: 'Proxy',
4 | type: 'structural',
5 | hint: 'An object representing another object',
6 | definition: `Provide a surrogate or placeholder for another object to control access to it.`,
7 | when: ``,
8 | codeES5: `function Car() {
9 | this.drive = function() {
10 | return 'driving';
11 | };
12 | }
13 |
14 | function CarPattern(driver) {
15 | this.driver = driver;
16 | this.drive = function() {
17 | if (driver.age < 18) return 'too young to drive';
18 |
19 | return new Car().drive();
20 | };
21 | }
22 |
23 | function Driver(age) {
24 | this.age = age;
25 | }
26 |
27 | module.exports = [Car, CarPattern, Driver];`,
28 | codeES6: `class Car {
29 | drive() {
30 | return 'driving';
31 | }
32 | }
33 |
34 | class CarPattern {
35 | constructor(driver) {
36 | this.driver = driver;
37 | }
38 |
39 | drive() {
40 | return this.driver.age < 18 ? 'too young to drive' : new Car().drive();
41 | }
42 | }
43 |
44 | class Driver {
45 | constructor(age) {
46 | this.age = age;
47 | }
48 | }
49 |
50 | export { Car, CarPattern, Driver };`
51 | };
52 |
53 | export default PROXY;
54 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | createStore,
3 | applyMiddleware,
4 | compose
5 | } from 'redux';
6 | const uuid = require('uuid/v4');
7 | import reducer from '../reducers/index';
8 | import patterns from '../static/patterns';
9 | import middleware from '../middleware';
10 | import {
11 | loadState,
12 | saveState
13 | } from '../helpers/localStorage';
14 |
15 | export const answers = patterns.map(pattern => ({
16 | ...pattern,
17 | answered: false,
18 | correct: null,
19 | answerId: null,
20 | uuid: uuid()
21 | }));
22 |
23 | export const initialProgress = {
24 | answers: [],
25 | remaining: answers,
26 | current: answers[0]
27 | };
28 |
29 | export const initialState = {
30 | js: 'es5',
31 | mode: 'dark',
32 | intro: true,
33 | patterns: answers,
34 | progress: initialProgress
35 | };
36 |
37 | const state = {
38 | ...initialState,
39 | ...loadState()
40 | };
41 |
42 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
43 |
44 | const store = createStore(reducer, state, composeEnhancers(applyMiddleware(...middleware)));
45 |
46 | store.subscribe(() => {
47 | const currentState = store.getState();
48 | saveState({
49 | mode: currentState.mode,
50 | js: currentState.js
51 | });
52 | });
53 |
54 | export default store;
55 |
--------------------------------------------------------------------------------
/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyle = createGlobalStyle`
4 | body {
5 | background: ${props => props.theme.background};
6 | font-family: 'Karla', sans-serif;
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | #root {
12 | justify-content: start;
13 | max-width: 640px;
14 | margin: auto;
15 | padding: 0 1rem 3rem;
16 |
17 | @media (min-width: 769px) {
18 | justify-content: space-between;
19 | padding: 0 0 3rem;
20 | }
21 | }
22 |
23 | .fixed {
24 | height: 375px
25 | }
26 | `;
27 |
28 | export default GlobalStyle;
29 |
--------------------------------------------------------------------------------
/src/styles/hljs/hljs.dark.js:
--------------------------------------------------------------------------------
1 | const styles = {
2 | "hljs": {
3 | border: '1px solid #555',
4 | display: 'block',
5 | overflowX: 'auto',
6 | padding: '1em',
7 | background: '#282828',
8 | color: '#ebdbb2'
9 | },
10 | 'hljs-subst': {
11 | color: '#ebdbb2'
12 | },
13 | 'hljs-deletion': {
14 | color: '#fb4934'
15 | },
16 | 'hljs-formula': {
17 | color: '#fb4934'
18 | },
19 | 'hljs-keyword': {
20 | color: '#fb4934'
21 | },
22 | 'hljs-link': {
23 | color: '#fb4934'
24 | },
25 | 'hljs-selector-tag': {
26 | color: '#fb4934'
27 | },
28 | 'hljs-built_in': {
29 | color: '#83a598'
30 | },
31 | 'hljs-emphasis': {
32 | color: '#83a598',
33 | fontStyle: 'italic'
34 | },
35 | 'hljs-name': {
36 | color: '#83a598'
37 | },
38 | 'hljs-quote': {
39 | color: '#83a598'
40 | },
41 | 'hljs-strong': {
42 | color: '#83a598',
43 | fontWeight: 'bold'
44 | },
45 | 'hljs-title': {
46 | color: '#83a598'
47 | },
48 | 'hljs-variable': {
49 | color: '#83a598'
50 | },
51 | 'hljs-attr': {
52 | color: '#fabd2f'
53 | },
54 | 'hljs-params': {
55 | color: '#fabd2f'
56 | },
57 | 'hljs-template-tag': {
58 | color: '#fabd2f'
59 | },
60 | 'hljs-type': {
61 | color: '#fabd2f'
62 | },
63 | 'hljs-builtin-name': {
64 | color: '#8f3f71'
65 | },
66 | 'hljs-doctag': {
67 | color: '#8f3f71'
68 | },
69 | 'hljs-literal': {
70 | color: '#d3869b'
71 | },
72 | 'hljs-number': {
73 | color: '#d3869b'
74 | },
75 | 'hljs-code': {
76 | color: '#fe8019'
77 | },
78 | 'hljs-meta': {
79 | color: '#fe8019'
80 | },
81 | 'hljs-regexp': {
82 | color: '#fe8019'
83 | },
84 | 'hljs-selector-id': {
85 | color: '#fe8019'
86 | },
87 | 'hljs-template-variable': {
88 | color: '#fe8019'
89 | },
90 | 'hljs-addition': {
91 | color: '#b8bb26'
92 | },
93 | 'hljs-meta-string': {
94 | color: '#b8bb26'
95 | },
96 | 'hljs-section': {
97 | color: '#b8bb26',
98 | fontWeight: 'bold'
99 | },
100 | 'hljs-selector-attr': {
101 | color: '#b8bb26'
102 | },
103 | 'hljs-selector-class': {
104 | color: '#b8bb26'
105 | },
106 | 'hljs-string': {
107 | color: '#b8bb26'
108 | },
109 | 'hljs-symbol': {
110 | color: '#b8bb26'
111 | },
112 | 'hljs-attribute': {
113 | color: '#8ec07c'
114 | },
115 | 'hljs-bullet': {
116 | color: '#8ec07c'
117 | },
118 | 'hljs-class': {
119 | color: '#8ec07c'
120 | },
121 | 'hljs-function': {
122 | color: '#8ec07c'
123 | },
124 | 'hljs-function .hljs-keyword': {
125 | color: '#8ec07c'
126 | },
127 | 'hljs-meta-keyword': {
128 | color: '#8ec07c'
129 | },
130 | 'hljs-selector-pseudo': {
131 | color: '#8ec07c'
132 | },
133 | 'hljs-tag': {
134 | color: '#8ec07c',
135 | fontWeight: 'bold'
136 | },
137 | 'hljs-comment': {
138 | color: '#928374',
139 | fontStyle: 'italic'
140 | },
141 | 'hljs-link_label': {
142 | color: '#d3869b'
143 | }
144 | };
145 |
146 | export default styles;
147 |
--------------------------------------------------------------------------------
/src/styles/hljs/hljs.light.js:
--------------------------------------------------------------------------------
1 | const styles = {
2 | "hljs": {
3 | border: '1px solid #d8d8d8',
4 | display: 'block',
5 | overflowX: 'auto',
6 | padding: '1em',
7 | color: '#383a42',
8 | background: '#fafafa'
9 | },
10 | 'hljs-comment': {
11 | color: '#a0a1a7',
12 | fontStyle: 'italic'
13 | },
14 | 'hljs-quote': {
15 | color: '#a0a1a7',
16 | fontStyle: 'italic'
17 | },
18 | 'hljs-doctag': {
19 | color: '#a626a4'
20 | },
21 | 'hljs-keyword': {
22 | color: '#a626a4'
23 | },
24 | 'hljs-formula': {
25 | color: '#a626a4'
26 | },
27 | 'hljs-section': {
28 | color: '#e45649'
29 | },
30 | 'hljs-name': {
31 | color: '#e45649'
32 | },
33 | 'hljs-selector-tag': {
34 | color: '#e45649'
35 | },
36 | 'hljs-deletion': {
37 | color: '#e45649'
38 | },
39 | 'hljs-subst': {
40 | color: '#e45649'
41 | },
42 | 'hljs-literal': {
43 | color: '#0184bb'
44 | },
45 | 'hljs-string': {
46 | color: '#50a14f'
47 | },
48 | 'hljs-regexp': {
49 | color: '#50a14f'
50 | },
51 | 'hljs-addition': {
52 | color: '#50a14f'
53 | },
54 | 'hljs-attribute': {
55 | color: '#50a14f'
56 | },
57 | 'hljs-meta-string': {
58 | color: '#50a14f'
59 | },
60 | 'hljs-built_in': {
61 | color: '#c18401'
62 | },
63 | 'hljs-class .hljs-title': {
64 | color: '#c18401'
65 | },
66 | 'hljs-attr': {
67 | color: '#986801'
68 | },
69 | 'hljs-variable': {
70 | color: '#986801'
71 | },
72 | 'hljs-template-variable': {
73 | color: '#986801'
74 | },
75 | 'hljs-type': {
76 | color: '#986801'
77 | },
78 | 'hljs-selector-class': {
79 | color: '#986801'
80 | },
81 | 'hljs-selector-attr': {
82 | color: '#986801'
83 | },
84 | 'hljs-selector-pseudo': {
85 | color: '#986801'
86 | },
87 | 'hljs-number': {
88 | color: '#986801'
89 | },
90 | 'hljs-symbol': {
91 | color: '#4078f2'
92 | },
93 | 'hljs-bullet': {
94 | color: '#4078f2'
95 | },
96 | 'hljs-link': {
97 | color: '#4078f2',
98 | textDecoration: 'underline'
99 | },
100 | 'hljs-meta': {
101 | color: '#4078f2'
102 | },
103 | 'hljs-selector-id': {
104 | color: '#4078f2'
105 | },
106 | 'hljs-title': {
107 | color: '#4078f2'
108 | },
109 | 'hljs-emphasis': {
110 | fontStyle: 'italic'
111 | },
112 | 'hljs-strong': {
113 | fontWeight: 'bold'
114 | }
115 | };
116 |
117 | export default styles;
118 |
--------------------------------------------------------------------------------
/src/styles/themes/theme.common.js:
--------------------------------------------------------------------------------
1 | import * as C from '../../static/constants/colors';
2 |
3 | export const themeCommon = {
4 | ATLANTIS: C.ATLANTIS,
5 | ALTO: C.ALTO,
6 | CRIMSON: C.CRIMSON
7 | };
8 |
--------------------------------------------------------------------------------
/src/styles/themes/theme.dark.js:
--------------------------------------------------------------------------------
1 | import { THEME_DARK } from '../../static/constants/themes';
2 | import * as C from '../../static/constants/colors';
3 |
4 | export const themeDark = {
5 | name: THEME_DARK,
6 |
7 | background: C.COAL_MINE,
8 |
9 | // button
10 | buttonBackground: C.TUNDORA,
11 | buttonBorder: C.TUNDORA,
12 | buttonColor: C.WILD_SAND,
13 |
14 | buttonBackgroundHover: C.GRAY,
15 | buttonBorderHover: C.GRAY,
16 | buttonColorHover: C.WHITE,
17 |
18 | // title
19 | title: C.CRIMSON,
20 | titleBackground: C.MINE_SHAFT,
21 |
22 | // menu link
23 | link: C.SILVER,
24 | active: C.WHITE,
25 |
26 | // header toggle
27 | toggleBackground: C.TUNDORA,
28 | toggleBorder: C.DOVE,
29 | toggleFill: C.SILVER,
30 |
31 | toggleBackgroundHover: C.DOVE,
32 | toggleFillHover: C.WILD_SAND,
33 |
34 | toggleActiveBackground: C.SILVER,
35 | toggleActiveFill: C.TUNDORA,
36 | toggleActiveBorder: C.SILVER,
37 |
38 | // text and header
39 | header: C.WILD_SAND,
40 | text: C.SILVER,
41 |
42 | border: C.TUNDORA
43 | };
44 |
--------------------------------------------------------------------------------
/src/styles/themes/theme.light.js:
--------------------------------------------------------------------------------
1 | import { THEME_LIGHT } from '../../static/constants/themes';
2 | import * as C from '../../static/constants/colors';
3 |
4 | export const themeLight = {
5 | name: THEME_LIGHT,
6 |
7 | background: C.WHITE,
8 |
9 | // button
10 | buttonBackground: C.PRIM,
11 | buttonBorder: C.ORCHID,
12 | buttonColor: C.PLUM,
13 |
14 | buttonBackgroundHover: C.PLUM,
15 | buttonBorderHover: C.PLUM,
16 | buttonColorHover: C.WHITE,
17 |
18 | // title
19 | title: C.PLUM,
20 | titleBackground: C.PRIM,
21 |
22 | // menu link
23 | link: C.PLUM,
24 | active: C.ORCHID,
25 |
26 | // header toggle
27 | toggleBackground: C.PRIM,
28 | toggleBorder: C.ORCHID,
29 | toggleFill: C.BOUQUET,
30 |
31 | toggleBackgroundHover: C.ORCHID,
32 | toggleFillHover: C.PLUM,
33 |
34 | toggleActiveBackground: C.BOUQUET,
35 | toggleActiveFill: C.PRIM,
36 | toggleActiveBorder: C.PLUM,
37 |
38 | // text and header
39 | header: C.PLUM,
40 | text: C.PLUM,
41 |
42 | border: C.PRIM
43 | };
44 |
--------------------------------------------------------------------------------
/static/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zoltantothcom/Design-Patterns-JavaScript/2c7ef902dbefb8a7a2ecea407ac7e8e6682f5b0a/static/screenshot.png
--------------------------------------------------------------------------------
/stories/Button.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Provider from './Provider.js';
3 | import { action } from '@storybook/addon-actions';
4 | import { storiesOf } from '@storybook/react';
5 | import { withKnobs, text } from '@storybook/addon-knobs';
6 | import Button from '../src/components/Button';
7 | import store from '../src/store';
8 |
9 | const withProvider = story => {story()} ;
10 |
11 | storiesOf('Button', module)
12 | .addDecorator(withProvider)
13 | .addDecorator(withKnobs)
14 |
15 | .add('default', () => (
16 |
17 | ));
18 |
--------------------------------------------------------------------------------
/stories/Percentage.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { Percentage } from '../src/components/Percentage';
4 |
5 | const initialArray = (n, isAnswered, isCorrect) => {
6 | let arr = Array(...Array(n));
7 |
8 | return arr.map((x, i) => {
9 | return {
10 | patternId: null,
11 | answerId: null,
12 | answered: isAnswered,
13 | correct: isCorrect,
14 | uuid: i
15 | };
16 | });
17 | };
18 |
19 | const answers0 = initialArray(23, true, false);
20 | const answers50 = [...initialArray(11, true, false), ...initialArray(12, true, true)];
21 | const answers100 = initialArray(23, true, true);
22 |
23 | storiesOf('Percentage', module)
24 | .add('under 40', () => )
25 | .add('between 40 and 70', () => )
26 | .add('over 70', () => );
27 |
--------------------------------------------------------------------------------
/stories/ProgressBar.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { ProgressBar } from '../src/components/ProgressBar';
4 |
5 | const initialArray = (n, isAnswered, isCorrect) => {
6 | let arr = Array(...Array(n));
7 |
8 | return arr.map((x, i) => {
9 | return {
10 | patternId: null,
11 | answerId: null,
12 | answered: isAnswered,
13 | correct: isCorrect,
14 | uuid: i
15 | };
16 | });
17 | };
18 |
19 | const answersDefault = initialArray(23);
20 | const answersSuccess = initialArray(23, true, true);
21 | const answersError = initialArray(23, true, false);
22 |
23 | storiesOf('ProgressBar', module)
24 | .add('unanswered questions', () => )
25 | .add('all questions correct', () => )
26 | .add('all questions wrong', () => );
27 |
--------------------------------------------------------------------------------
/stories/Provider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Provider } from 'react-redux';
4 |
5 | const ProviderWrapper = ({ children, store }) => {
6 | return {children} ;
7 | };
8 |
9 | ProviderWrapper.propTypes = {
10 | children: PropTypes.object.isRequired,
11 | store: PropTypes.object.isRequired
12 | };
13 |
14 | export default ProviderWrapper;
15 |
--------------------------------------------------------------------------------
/stories/Title.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { Title } from '../src/components/Title';
4 |
5 | storiesOf('Title', module).add('default', () => );
6 |
--------------------------------------------------------------------------------
/stories/Toggle.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Provider from './Provider.js';
3 | import { storiesOf } from '@storybook/react';
4 | import Toggle from '../src/components/Toggle';
5 | import store from '../src/store';
6 |
7 | const withProvider = story => {story()} ;
8 |
9 | storiesOf('Toggle', module)
10 | .addDecorator(withProvider)
11 |
12 | .add('js', () => )
13 | .add('mode', () => );
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4 |
5 | module.exports = {
6 | mode: 'development',
7 | devtool: 'inline-source-map',
8 | entry: './index.js',
9 | output: {
10 | path: path.join(__dirname, '/build'),
11 | publicPath: '/',
12 | filename: '[name].js'
13 | },
14 | optimization: {
15 | splitChunks: {
16 | chunks: 'all'
17 | }
18 | },
19 | devServer: {
20 | historyApiFallback: true,
21 | contentBase: './build'
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.jsx?$/,
27 | exclude: /node_modules/,
28 | use: [
29 | {
30 | loader: 'babel-loader'
31 | },
32 | {
33 | loader: 'eslint-loader',
34 | options: {
35 | formatter: require('eslint/lib/cli-engine/formatters/stylish')
36 | }
37 | }
38 | ]
39 | }
40 | ]
41 | },
42 | plugins: [
43 | new HtmlWebpackPlugin({
44 | template: 'index.html'
45 | }),
46 | new HtmlWebpackPlugin({
47 | filename: '200.html',
48 | template: 'index.html'
49 | }),
50 | new BundleAnalyzerPlugin({
51 | analyzerMode: 'disabled',
52 | generateStatsFile: true,
53 | statsOptions: { source: false },
54 | statsFilename: path.join(__dirname, 'stats/stats.json')
55 | })
56 | ],
57 | resolve: {
58 | extensions: ['.js', '.jsx']
59 | },
60 | performance: { hints: false }
61 | };
62 |
--------------------------------------------------------------------------------