epam-ai-conductor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +46 -0
  2. package/bin/workshop.js +6 -0
  3. package/dist/auth-check.d.ts +1 -0
  4. package/dist/auth-check.js +45 -0
  5. package/dist/auth-check.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +156 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/content/content/module-1/task/module-1_home-task_components.md +257 -0
  10. package/dist/content/content/module-1/task/module-1_home-task_good_practices.md +69 -0
  11. package/dist/content/content/module-1/task/module-1_home-task_task.md +253 -0
  12. package/dist/content/content/module-1/tests/Button.test.tsx +22 -0
  13. package/dist/content/content/module-1/tests/CourseCard.test.tsx +64 -0
  14. package/dist/content/content/module-1/tests/CourseInfo.test.tsx +73 -0
  15. package/dist/content/content/module-1/tests/Courses.test.tsx +87 -0
  16. package/dist/content/content/module-1/tests/Header.test.tsx +23 -0
  17. package/dist/content/content/module-1/tests/Input.test.tsx +36 -0
  18. package/dist/content/content/module-1/tests/helpers.test.ts +13 -0
  19. package/dist/content/content/module-1/theory/module-1_elements-render.md +66 -0
  20. package/dist/content/content/module-1/theory/module-1_hooks_useEffect.md +178 -0
  21. package/dist/content/content/module-1/theory/module-1_hooks_useState.md +131 -0
  22. package/dist/content/content/module-1/theory/module-1_react.md +64 -0
  23. package/dist/content/content/module-1/theory/module-1_spa.md +74 -0
  24. package/dist/content/content/module-2/task/module-2_home-task_components.md +313 -0
  25. package/dist/content/content/module-2/task/module-2_home-task_good_practices.md +35 -0
  26. package/dist/content/content/module-2/task/module-2_home-task_task.md +210 -0
  27. package/dist/content/content/module-2/tests/App.test.tsx +54 -0
  28. package/dist/content/content/module-2/tests/Login.test.tsx +73 -0
  29. package/dist/content/content/module-2/tests/Registration.test.tsx +70 -0
  30. package/dist/content/content/module-2/tests/SearchBar.test.tsx +39 -0
  31. package/dist/content/content/module-2/theory/module-2_custom-hooks.md +201 -0
  32. package/dist/content/content/module-2/theory/module-2_hooks.md +117 -0
  33. package/dist/content/content/module-2/theory/module-2_react-router.md +328 -0
  34. package/dist/content/content/module-3/task/module-3_home-task_components.md +94 -0
  35. package/dist/content/content/module-3/task/module-3_home-task_good_practices.md +26 -0
  36. package/dist/content/content/module-3/task/module-3_home-task_task.md +170 -0
  37. package/dist/content/content/module-3/tests/App.test.tsx +54 -0
  38. package/dist/content/content/module-3/tests/Courses.test.tsx +87 -0
  39. package/dist/content/content/module-3/tests/CreateAuthor.test.tsx +44 -0
  40. package/dist/content/content/module-3/tests/CreateCourse.test.tsx +122 -0
  41. package/dist/content/content/module-3/theory/module-3_redux-hooks.md +194 -0
  42. package/dist/content/content/module-3/theory/module-3_state-actions-reducers.md +445 -0
  43. package/dist/content/content/module-4/task/module-4_home-task_components.md +187 -0
  44. package/dist/content/content/module-4/task/module-4_home-task_task.md +139 -0
  45. package/dist/content/content/module-4/tests/App.test.tsx +54 -0
  46. package/dist/content/content/module-4/tests/Courses.test.tsx +87 -0
  47. package/dist/content/content/module-4/tests/CreateCourse.test.tsx +122 -0
  48. package/dist/content/content/module-4/tests/Login.test.tsx +73 -0
  49. package/dist/content/content/module-4/theory/module-4_async-redux.md +99 -0
  50. package/dist/content/content/module-4/theory/module-4_private-routes.md +55 -0
  51. package/dist/content/content/module-5/task/module-5_home-task_instruction.md +68 -0
  52. package/dist/content/content/module-5/task/module-5_home-task_task.md +154 -0
  53. package/dist/content/content/module-5/tests/App.test.tsx +54 -0
  54. package/dist/content/content/module-5/tests/CourseCard.test.tsx +64 -0
  55. package/dist/content/content/module-5/tests/Courses.test.tsx +87 -0
  56. package/dist/content/content/module-5/tests/Header.test.tsx +23 -0
  57. package/dist/content/content/module-5/theory/module-5_react-testing-library_example.md +379 -0
  58. package/dist/content/content/module-5/theory/module-5_redux-writing-tests.md +246 -0
  59. package/dist/content/module-1/task/module-1_home-task_components.md +257 -0
  60. package/dist/content/module-1/task/module-1_home-task_good_practices.md +69 -0
  61. package/dist/content/module-1/task/module-1_home-task_task.md +253 -0
  62. package/dist/content/module-1/tests/Button.test.tsx +22 -0
  63. package/dist/content/module-1/tests/CourseCard.test.tsx +64 -0
  64. package/dist/content/module-1/tests/CourseInfo.test.tsx +73 -0
  65. package/dist/content/module-1/tests/Courses.test.tsx +87 -0
  66. package/dist/content/module-1/tests/Header.test.tsx +23 -0
  67. package/dist/content/module-1/tests/Input.test.tsx +36 -0
  68. package/dist/content/module-1/tests/helpers.test.ts +13 -0
  69. package/dist/content/module-1/theory/module-1_elements-render.md +66 -0
  70. package/dist/content/module-1/theory/module-1_hooks_useEffect.md +178 -0
  71. package/dist/content/module-1/theory/module-1_hooks_useState.md +131 -0
  72. package/dist/content/module-1/theory/module-1_react.md +64 -0
  73. package/dist/content/module-1/theory/module-1_spa.md +74 -0
  74. package/dist/content/module-2/task/module-2_home-task_components.md +313 -0
  75. package/dist/content/module-2/task/module-2_home-task_good_practices.md +35 -0
  76. package/dist/content/module-2/task/module-2_home-task_task.md +210 -0
  77. package/dist/content/module-2/tests/App.test.tsx +54 -0
  78. package/dist/content/module-2/tests/Login.test.tsx +73 -0
  79. package/dist/content/module-2/tests/Registration.test.tsx +70 -0
  80. package/dist/content/module-2/tests/SearchBar.test.tsx +39 -0
  81. package/dist/content/module-2/theory/module-2_custom-hooks.md +201 -0
  82. package/dist/content/module-2/theory/module-2_hooks.md +117 -0
  83. package/dist/content/module-2/theory/module-2_react-router.md +328 -0
  84. package/dist/content/module-3/task/module-3_home-task_components.md +94 -0
  85. package/dist/content/module-3/task/module-3_home-task_good_practices.md +26 -0
  86. package/dist/content/module-3/task/module-3_home-task_task.md +170 -0
  87. package/dist/content/module-3/tests/App.test.tsx +54 -0
  88. package/dist/content/module-3/tests/Courses.test.tsx +87 -0
  89. package/dist/content/module-3/tests/CreateAuthor.test.tsx +44 -0
  90. package/dist/content/module-3/tests/CreateCourse.test.tsx +122 -0
  91. package/dist/content/module-3/theory/module-3_redux-hooks.md +194 -0
  92. package/dist/content/module-3/theory/module-3_state-actions-reducers.md +445 -0
  93. package/dist/content/module-4/task/module-4_home-task_components.md +187 -0
  94. package/dist/content/module-4/task/module-4_home-task_task.md +139 -0
  95. package/dist/content/module-4/tests/App.test.tsx +54 -0
  96. package/dist/content/module-4/tests/Courses.test.tsx +87 -0
  97. package/dist/content/module-4/tests/CreateCourse.test.tsx +122 -0
  98. package/dist/content/module-4/tests/Login.test.tsx +73 -0
  99. package/dist/content/module-4/theory/module-4_async-redux.md +99 -0
  100. package/dist/content/module-4/theory/module-4_private-routes.md +55 -0
  101. package/dist/content/module-5/task/module-5_home-task_instruction.md +68 -0
  102. package/dist/content/module-5/task/module-5_home-task_task.md +154 -0
  103. package/dist/content/module-5/tests/App.test.tsx +54 -0
  104. package/dist/content/module-5/tests/CourseCard.test.tsx +64 -0
  105. package/dist/content/module-5/tests/Courses.test.tsx +87 -0
  106. package/dist/content/module-5/tests/Header.test.tsx +23 -0
  107. package/dist/content/module-5/theory/module-5_react-testing-library_example.md +379 -0
  108. package/dist/content/module-5/theory/module-5_redux-writing-tests.md +246 -0
  109. package/dist/content-loader.d.ts +7 -0
  110. package/dist/content-loader.js +26 -0
  111. package/dist/content-loader.js.map +1 -0
  112. package/dist/context-builder.d.ts +2 -0
  113. package/dist/context-builder.js +116 -0
  114. package/dist/context-builder.js.map +1 -0
  115. package/package.json +40 -0
@@ -0,0 +1,379 @@
1
+ ---
2
+ sidebar_position: 2
3
+ sidebar_label: 'React Testing Library: Examples'
4
+ title: 'Examples of Tests'
5
+ ---
6
+
7
+ In this section, you will learn how to render a React component in test with [React Testing Library](https://github.com/testing-library/react-testing-library).
8
+
9
+ ## Rendering a component
10
+
11
+ We will use the following App [function component](https://www.robinwieruch.de/react-function-component/) from a `src/App.js` file:
12
+
13
+ ```jsx title="src/App.js"
14
+ const title = 'React Testing Library Example';
15
+
16
+ function App() {
17
+ return <div>{title}</div>;
18
+ }
19
+
20
+ export default App;
21
+ ```
22
+
23
+ And test it in a `src/App.test.js` file:
24
+
25
+ ```jsx title="src/App.test.js"
26
+ import { render } from '@testing-library/react';
27
+
28
+ import App from './App';
29
+
30
+ describe('App', () => {
31
+ test('renders App component', () => {
32
+ render(<App />);
33
+ });
34
+ });
35
+ ```
36
+
37
+ The test code above used React Testing Library's `render` method to virtually render the App component imported from `App.js` file and append it to the `document.body` node. You can access the rendered HTML through the `screen` object. This object also has the DOM testing methods already bound into it.
38
+
39
+ RTL's render function takes any [JSX](../module-1/jsx.mdx) to render it. Afterward, you should have access to the React component in your test. To convince yourself that it's there, you can use RTL's [debug](https://subscription.packtpub.com/book/web_development/9781800564459/2/ch02lvl1sec14/using-the-debug-method) function:
40
+
41
+ ```jsx title="src/App.test.js"
42
+ // highlight-next-line
43
+ import { render, screen } from '@testing-library/react';
44
+
45
+ import App from './App';
46
+
47
+ describe('App', () => {
48
+ test('renders App component', () => {
49
+ render(<App />);
50
+
51
+ // highlight-next-line
52
+ screen.debug();
53
+ });
54
+ });
55
+ ```
56
+
57
+ After running your test on the command line, you should see the HTML output of your App component. Whenever you write a test for a component with React Testing library, you can render the component first and then debug what's visible for RTL's renderer in the test. This way, you can write your test with more confidence:
58
+
59
+ ```html
60
+ <body>
61
+ <div>
62
+ <div>
63
+ React Testing Library Example
64
+ </div>
65
+ </div>
66
+ </body>
67
+ ```
68
+
69
+ The great thing about it, React Testing Library doesn't care much about the actual components. Let's take the following React components which utilize different React features ([useState](../module-1/hooks/useState.md), [event handler](https://www.robinwieruch.de/react-event-handler/), [props]("../module-1/components-and-props.md")) and concepts ([controlled component](https://www.robinwieruch.de/react-controlled-components/)):
70
+
71
+ ```jsx
72
+ function App() {
73
+ const [search, setSearch] = React.useState('');
74
+
75
+ function handleChange(event) {
76
+ setSearch(event.target.value);
77
+ }
78
+
79
+ return (
80
+ <div>
81
+ <Search value={search} onChange={handleChange}>
82
+ Search:
83
+ </Search>
84
+
85
+ <p>Searches for {search ? search : '...'}</p>
86
+ </div>
87
+ );
88
+ }
89
+
90
+ function Search({ value, onChange, children }) {
91
+ return (
92
+ <div>
93
+ <label htmlFor="search">{children}</label>
94
+ <input
95
+ id="search"
96
+ type="text"
97
+ value={value}
98
+ onChange={onChange}
99
+ />
100
+ </div>
101
+ );
102
+ }
103
+
104
+ export default App;
105
+ ```
106
+
107
+ If you start the test of your App component again, you should see the following output from the `debug` function:
108
+
109
+ ```html
110
+ <body>
111
+ <div>
112
+ <div>
113
+ <div>
114
+ <label
115
+ for="search"
116
+ >
117
+ Search:
118
+ </label>
119
+ <input
120
+ id="search"
121
+ type="text"
122
+ value=""
123
+ />
124
+ </div>
125
+ <p>
126
+ Searches for
127
+ ...
128
+ </p>
129
+ </div>
130
+ </div>
131
+ </body>
132
+ ```
133
+
134
+ React Testing Library is used to interact with your React components like a human being. What a human being sees is just rendered HTML from your React components, so that's why you see this HTML structure as output rather than two individual React components.
135
+
136
+ ## Selecting elements
137
+
138
+ Let's learn about how to grab elements which are then used for assertions or for user interactions:
139
+
140
+ ```jsx
141
+ import React from 'react';
142
+ import { render, screen } from '@testing-library/react';
143
+
144
+ import App from './App';
145
+
146
+ describe('App', () => {
147
+ test('renders App component', () => {
148
+ render(<App />);
149
+
150
+ // highlight-next-line
151
+ screen.getByText('Search:');
152
+ });
153
+ });
154
+ ```
155
+
156
+ And this is an example of the assertion that checks whether the element is in the DOM:
157
+
158
+ ```jsx
159
+ import React from 'react';
160
+ import { render, screen } from '@testing-library/react';
161
+
162
+ import App from './App';
163
+
164
+ describe('App', () => {
165
+ test('renders App component', () => {
166
+ render(<App />);
167
+
168
+ // highlight-next-line
169
+ expect(screen.getByText('Search:')).toBeInTheDocument();
170
+ });
171
+ });
172
+ ```
173
+
174
+ Conveniently `getByText` throws an error by default if the element cannot be found. This is useful for giving you a hint while writing the test that the selected element isn't there in the first place. A few people exploit this behavior to use search functions like `getByText` as implicit assertion replacement instead of an explicit assertion with expect.
175
+
176
+ Most of React test cases should use methods for finding elements. RTL provides you with several methods to find an element by specific attributes in addition to the `getByText()` method above:
177
+
178
+ * `getByText()`: find the element by its `textContent` value
179
+ * `getByRole()`: by its `role` attribute value
180
+ * `getByLabelText()`: by its `label` attribute value
181
+ * `getByPlaceholderText()`: by its `placeholder` attribute value
182
+ * `getByAltText()`: by its `alt` attribute value
183
+ * `getByDisplayValue()`: by its `value` attribute, usually for `<input>` elements
184
+ * `getByTitle()`: by its `title` attribute value
185
+
186
+ And when these methods are not enough, you can use the `getByTestId()` method, which allows you to find an element by its `data-testid` attribute:
187
+
188
+ ```jsx
189
+ import { render, screen } from '@testing-library/react';
190
+
191
+ render(<div data-testid="test-element" />);
192
+ const element = screen.getByTestId('test-element');
193
+ ```
194
+
195
+ :::tip
196
+ But since selecting elements using `data-testid` attributes doesn't resemble how a real user would use your application, the documentation recommends you use it only as the last resort when all other methods fail to find your element. Generally, finding by Text, Role, or Label should cover most cases.
197
+ :::
198
+
199
+ ## Search variants
200
+
201
+ One of the search variants in RTL is `getBy` which is used for `getByText` or `getByRole`. This is also the search variant which is used by default when testing React components.
202
+
203
+ Two other search variants are `queryBy` and `findBy`; which both can get extended by the same search types that `getBy` has access to. For example, `queryBy` with all its search types:
204
+
205
+ * queryByText
206
+ * queryByRole
207
+ * queryByLabelText
208
+ * queryByPlaceholderText
209
+ * queryByAltText
210
+ * queryByDisplayValue
211
+
212
+ And `findBy` with all its search types:
213
+
214
+ * findByText
215
+ * findByRole
216
+ * findByLabelText
217
+ * findByPlaceholderText
218
+ * findByAltText
219
+ * findByDisplayValue
220
+
221
+ ### What's the difference between getBy vs queryBy?
222
+
223
+ You already know that `getBy` returns an element or an error. It's a convenient side-effect of `getBy` that it returns an error, because it makes sure that we as developers notice early that there is something wrong in our test. However, this makes it difficult to check for elements which shouldn't be there:
224
+
225
+ ```jsx
226
+ import React from 'react';
227
+ import { render, screen } from '@testing-library/react';
228
+
229
+ import App from './App';
230
+
231
+ describe('App', () => {
232
+ test('renders App component', () => {
233
+ render(<App />);
234
+
235
+ screen.debug();
236
+
237
+ // fails
238
+ expect(screen.getByText(/Searches for JavaScript/)).toBeNull();
239
+ });
240
+ });
241
+ ```
242
+
243
+ This doesn't work, because, even though debug output shows that the element with the text "Searches for JavaScript" isn't there, `getBy` throws an error before we can make the assertion, because it cannot find the element with this text. In order to assert elements which aren't there, we can exchange `getBy` with `queryBy`:
244
+
245
+ ```jsx
246
+ import React from 'react';
247
+ import { render, screen } from '@testing-library/react';
248
+
249
+ import App from './App';
250
+
251
+ describe('App', () => {
252
+ test('renders App component', () => {
253
+ render(<App />);
254
+
255
+ // highlight-next-line
256
+ expect(screen.queryByText(/Searches for JavaScript/)).toBeNull();
257
+ });
258
+ });
259
+ ```
260
+
261
+ So every time you are asserting that an element isn't there, use `queryBy`. Otherwise default to `getBy`. So what about `findBy` then? The `findBy` search variant is used for asynchronous elements which will be there eventually.
262
+
263
+ ### What about multiple elements?
264
+
265
+ You have learned about the three search variants `getBy`, `queryBy` and `findBy`; which all can be associated with the search types (e.g. Text, Role, PlaceholderText, DisplayValue). If all of these search functions return only one element, how to assert if there are multiple elements (e.g. a list in a React component). All search variants can be extended with the `All` word:
266
+
267
+ * getAllBy
268
+ * queryAllBy
269
+ * findAllBy
270
+
271
+ Whereas all of them return an array of elements and can be associated with the search types again.
272
+
273
+ ### Query types summary table
274
+
275
+ <table>
276
+ <thead>
277
+ <tr>
278
+ <th>Type of Query</th>
279
+ <th>0 Matches</th>
280
+ <th>1 Match</th>
281
+ <th>&gt;1 Matches</th>
282
+ <th align="center">Retry (Async/Await)</th>
283
+ </tr>
284
+ </thead>
285
+ <tbody>
286
+ <tr>
287
+ <td><strong>Single Element</strong></td>
288
+ <td></td>
289
+ <td></td>
290
+ <td></td>
291
+ <td align="center"></td>
292
+ </tr>
293
+ <tr>
294
+ <td><code>getBy...</code></td>
295
+ <td>Throw error</td>
296
+ <td>Return element</td>
297
+ <td>Throw error</td>
298
+ <td align="center">No</td>
299
+ </tr>
300
+ <tr>
301
+ <td><code>queryBy...</code></td>
302
+ <td>Return <code>null</code></td>
303
+ <td>Return element</td>
304
+ <td>Throw error</td>
305
+ <td align="center">No</td>
306
+ </tr>
307
+ <tr>
308
+ <td><code>findBy...</code></td>
309
+ <td>Throw error</td>
310
+ <td>Return element</td>
311
+ <td>Throw error</td>
312
+ <td align="center">Yes</td>
313
+ </tr>
314
+ <tr>
315
+ <td><strong>Multiple Elements</strong></td>
316
+ <td></td>
317
+ <td></td>
318
+ <td></td>
319
+ <td align="center"></td>
320
+ </tr>
321
+ <tr>
322
+ <td><code>getAllBy...</code></td>
323
+ <td>Throw error</td>
324
+ <td>Return array</td>
325
+ <td>Return array</td>
326
+ <td align="center">No</td>
327
+ </tr>
328
+ <tr>
329
+ <td><code>queryAllBy...</code></td>
330
+ <td>Return <code>[]</code></td>
331
+ <td>Return array</td>
332
+ <td>Return array</td>
333
+ <td align="center">No</td>
334
+ </tr>
335
+ <tr>
336
+ <td><code>findAllBy...</code></td>
337
+ <td>Throw error</td>
338
+ <td>Return array</td>
339
+ <td>Return array</td>
340
+ <td align="center">Yes</td>
341
+ </tr>
342
+ </tbody>
343
+ </table>
344
+
345
+ ## Fire event
346
+
347
+ We can use RTL's `fireEvent` function to simulate interactions of an end user. Let's see how this works for our input field:
348
+
349
+ ```jsx
350
+ import React from 'react';
351
+ import { render, screen, fireEvent } from '@testing-library/react';
352
+
353
+ import App from './App';
354
+
355
+ describe('App', () => {
356
+ test('renders App component', () => {
357
+ render(<App />);
358
+
359
+ screen.debug();
360
+
361
+ fireEvent.change(screen.getByRole('textbox'), {
362
+ target: { value: 'JavaScript' },
363
+ });
364
+
365
+ screen.debug();
366
+ });
367
+ });
368
+ ```
369
+
370
+ The fireEvent function takes an element (here the input field by textbox role) and an event (here an event which has the value "JavaScript"). The debug function's output should show the HTML structure before and after the event; and you should see that the new value of the input field gets rendered appropriately.
371
+
372
+ ---
373
+
374
+ ## Materials
375
+ 1. [React Testing Library Examples](https://github.com/testing-library/react-testing-library#examples)
376
+ 2. [React Testing Library Example: Step-By-Step](https://testing-library.com/docs/react-testing-library/example-intro)
377
+ 3. [React Testing Library: Queries](https://testing-library.com/docs/react-testing-library/cheatsheet#queries)
378
+ 4. [Firing Events](https://testing-library.com/docs/dom-testing-library/api-events)
379
+ 5. [Testing Recipes](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)
@@ -0,0 +1,246 @@
1
+ ---
2
+ sidebar_position: 4
3
+ sidebar_label: 'Redux. Writing Tests'
4
+ title: Redux. Writing Tests
5
+ ---
6
+
7
+ The general advice for testing an app using Redux is as follows:
8
+
9
+ * Use integration tests for everything working together. I.e. for a React app using Redux, render a `<Provider>` with a real store instance wrapping the component/s being tested. Interactions with the page being tested should use real Redux logic, with API calls mocked out so app code doesn't have to change, and assert that the UI is updated appropriately.
10
+ * If needed, use basic unit tests for pure functions such as particularly complex reducers or selectors. However, in many cases, these are just implementation details that are covered by integration tests instead.
11
+
12
+ ## Testing Redux connected components
13
+
14
+ Let's say we have some components, which fetch user data and display it.
15
+
16
+ ```jsx
17
+ import React, { useState, useEffect } from "react";
18
+ import { getUserData } from "./api";
19
+
20
+ const User = () => {
21
+ const [userData, setUserData] = useState(null);
22
+
23
+ useEffect(() => {
24
+ getUserData.then((data) => {
25
+ setUserData(data);
26
+ });
27
+ });
28
+
29
+ if (!userData) {
30
+ return null;
31
+ }
32
+
33
+ return <div>{userData.name}</div>;
34
+ };
35
+
36
+ export default User;
37
+ ```
38
+
39
+ Now let's see how it can be tested
40
+
41
+ ```jsx
42
+ import React from "react";
43
+ import { screen, render } from "@testing-library/react";
44
+ import User from "./User";
45
+
46
+ jest.mock("./api", () => ({
47
+ getUserData: () => ({ name: "mock name" })
48
+ }));
49
+
50
+ describe("User", () => {
51
+ it("should display user name", async () => {
52
+ render(<User />);
53
+
54
+ const userName = await screen.findByText("mock name");
55
+
56
+ expect(userName).toBeTruthy();
57
+ });
58
+ });
59
+ ```
60
+
61
+ The first thing we have to do is mock our API call with `jest.mock`. Normally it would make a network request, but in tests, we need to mock it.
62
+
63
+ Then we use `render` function to render our component, and `screen.findByText` to search for text in the component we just rendered.
64
+
65
+ Now let's suppose we would need to access user data in other parts of the application. Let's move it to the Redux store. Here is how the refactored version of the component might look like.
66
+
67
+ ```jsx
68
+ import React, { useState, useEffect } from "react";
69
+ import { connect } from "react-redux";
70
+ import { fetchUserData } from './actions';
71
+
72
+ const User = ({ userData, fetchUserData }) => {
73
+ useEffect(() => {
74
+ fetchUserData();
75
+ }, []);
76
+
77
+ if (!userData) {
78
+ return null;
79
+ }
80
+
81
+ return <div>{userData.name}</div>;
82
+ };
83
+
84
+ const mapStateToProps = (state) => ({
85
+ userData: state.user
86
+ });
87
+
88
+ const mapDispatchToProps = {
89
+ fetchUserData,
90
+ };
91
+
92
+ export default connect(mapStateToProps, mapDispatchToProps)(User);
93
+ ```
94
+
95
+ Now the first thing you will notice in your test is: **'Could not find "store" in the context of "Connect(User)"'** error. It's because your component needs to be wrapper in `Provider` to access Redux Store. Let's fix our tests:
96
+
97
+ ```jsx
98
+ import React from "react";
99
+ import { screen, render } from "@testing-library/react";
100
+ import { createStore } from "redux";
101
+ import User from "./User";
102
+ import reducer from "./reducer";
103
+ import store from "./store";
104
+
105
+ jest.mock("./api", () => ({
106
+ getUserData: () => ({ name: "mock name" })
107
+ }));
108
+
109
+ const initialState = {
110
+ user: { name: "mock name" },
111
+ };
112
+
113
+ const store = createStore(reducer, initialState);
114
+
115
+ const Wrapper = ({ children }) => (
116
+ <Provider store={store}>{children}</Provider>
117
+ );
118
+
119
+ describe("User", () => {
120
+ it("should display user name", async () => {
121
+ render(<User />, { wrapper: Wrapper });
122
+
123
+ const userName = await screen.findByText("mock name");
124
+
125
+ expect(userName).toBeTruthy();
126
+ });
127
+ });
128
+ ```
129
+
130
+ We fixed the error by creating a Wrapper. This component will wrap the component we test with Provider and apply a mocked state. We can go one step further by custom render function using the one from React Testing Library.
131
+
132
+ ```jsx
133
+ import React from "react";
134
+ import { render as rtlRender } from "@testing-library/react";
135
+ import { createStore } from "redux";
136
+ import { Provider } from "react-redux";
137
+ import reducer from "./reducer";
138
+
139
+ export const renderWithState = (
140
+ ui,
141
+ { initialState, ...renderOptions } = {}
142
+ ) => {
143
+ const store = createStore(reducer, initialState);
144
+ const Wrapper = ({ children }) => (
145
+ <Provider store={store}>{children}</Provider>
146
+ );
147
+
148
+ return render(ui, { wrapper: Wrapper, ...renderOptions });
149
+ };
150
+ ```
151
+
152
+ And then in our case we can just import it and use like this:
153
+
154
+ ```jsx
155
+ renderWithState(<User />, { initialState });
156
+ ```
157
+
158
+ ## Reducers
159
+
160
+ Reducers are simple functions that give you the new state when you apply an action to the current state. Usually, you don't need to test reducers directly unless they have complex logic. But, if you want to be sure your reducer works as expected, testing is straightforward.
161
+
162
+ Since reducers are pure functions, testing them is simple. Just call the reducer with a certain starting state and action, then check if the resulting state is what you expect.
163
+ ```jsx
164
+ import { createSlice } from '@reduxjs/toolkit'
165
+
166
+ const initialState = [
167
+ {
168
+ text: 'Use Redux',
169
+ completed: false,
170
+ id: 0
171
+ }
172
+ ]
173
+
174
+ const todosSlice = createSlice({
175
+ name: 'todos',
176
+ initialState,
177
+ reducers: {
178
+ todoAdded(state, action: PayloadAction<string>) {
179
+ state.push({
180
+ id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
181
+ completed: false,
182
+ text: action.payload
183
+ })
184
+ }
185
+ }
186
+ })
187
+
188
+ export const { todoAdded } = todosSlice.actions
189
+
190
+ export default todosSlice.reducer
191
+ ```
192
+
193
+ We can test it like:
194
+
195
+ ```jsx
196
+ import reducer, { todoAdded } from './todosSlice'
197
+
198
+ test('should return the initial state', () => {
199
+ expect(reducer(undefined, {})).toEqual([
200
+ {
201
+ text: 'Use Redux',
202
+ completed: false,
203
+ id: 0
204
+ }
205
+ ])
206
+ })
207
+
208
+ test('should handle a todo being added to an empty list', () => {
209
+ const previousState = []
210
+ expect(reducer(previousState, todoAdded('Run the tests'))).toEqual([
211
+ {
212
+ text: 'Run the tests',
213
+ completed: false,
214
+ id: 0
215
+ }
216
+ ])
217
+ })
218
+
219
+ test('should handle a todo being added to an existing list', () => {
220
+ const previousState = [
221
+ {
222
+ text: 'Run the tests',
223
+ completed: true,
224
+ id: 0
225
+ }
226
+ ]
227
+ expect(reducer(previousState, todoAdded('Use Redux'))).toEqual([
228
+ {
229
+ text: 'Run the tests',
230
+ completed: true,
231
+ id: 0
232
+ },
233
+ {
234
+ text: 'Use Redux',
235
+ completed: false,
236
+ id: 1
237
+ }
238
+ ])
239
+ })
240
+ ```
241
+
242
+ ----
243
+
244
+ ## Materials
245
+ 1. [Redux tests](https://redux.js.org/usage/writing-tests)
246
+ 2. [How to test React-Redux connected Components](https://www.robinwieruch.de/react-connected-component-test)
@@ -0,0 +1,7 @@
1
+ export declare const TOTAL_MODULES = 5;
2
+ export interface ModuleContent {
3
+ theory: string;
4
+ task: string;
5
+ tests: string;
6
+ }
7
+ export declare function loadModule(n: number): ModuleContent;
@@ -0,0 +1,26 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const CONTENT_DIR = join(__dirname, 'content');
6
+ export const TOTAL_MODULES = 5;
7
+ function readDir(dir) {
8
+ if (!existsSync(dir))
9
+ return '';
10
+ return readdirSync(dir)
11
+ .sort()
12
+ .map(f => {
13
+ const label = f.replace(/^module-\d+_/, '').replace(/_/g, '/');
14
+ return `### ${label}\n\n${readFileSync(join(dir, f), 'utf-8')}`;
15
+ })
16
+ .join('\n\n---\n\n');
17
+ }
18
+ export function loadModule(n) {
19
+ const base = join(CONTENT_DIR, `module-${n}`);
20
+ return {
21
+ theory: readDir(join(base, 'theory')),
22
+ task: readDir(join(base, 'task')),
23
+ tests: readDir(join(base, 'tests')),
24
+ };
25
+ }
26
+ //# sourceMappingURL=content-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-loader.js","sourceRoot":"","sources":["../src/content-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAQ/B,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,OAAO,WAAW,CAAC,GAAG,CAAC;SACpB,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE;QACP,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/D,OAAO,OAAO,KAAK,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;IAClE,CAAC,CAAC;SACD,IAAI,CAAC,aAAa,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;IAC9C,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACrC,IAAI,EAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,KAAK,EAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const MODULE_TITLES: Record<number, string>;
2
+ export declare function buildContext(moduleNum: number, targetDir: string): void;