@powerhousedao/academy 5.1.0-dev.1 → 5.1.0-dev.10

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 (75) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/blog/BeyondCommunication-ABlueprintForDevelopment.md +2 -1
  3. package/blog/TheChallengeOfChange.md +1 -0
  4. package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +24 -27
  5. package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +118 -9
  6. package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +110 -28
  7. package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +191 -145
  8. package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +378 -0
  9. package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +560 -0
  10. package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +1 -1
  11. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +2 -2
  12. package/docs/academy/{01-GetStarted → 02-MasteryTrack/01-BuilderEnvironment}/05-VetraStudio.md +48 -6
  13. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +75 -44
  14. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +28 -22
  15. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +28 -31
  16. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +211 -206
  17. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +176 -62
  18. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +21 -0
  19. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +309 -319
  20. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +4 -0
  21. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +4 -0
  22. package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
  23. package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +111 -35
  24. package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +255 -76
  25. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +281 -160
  26. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +188 -35
  27. package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +95 -7
  28. package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
  29. package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +24 -0
  30. package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +211 -0
  31. package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +171 -0
  32. package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +462 -0
  33. package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +45 -0
  34. package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +422 -0
  35. package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +61 -0
  36. package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +384 -0
  37. package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +8 -0
  38. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +7 -0
  39. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +9 -0
  40. package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +6 -2
  41. package/docs/academy/04-APIReferences/01-ReactHooks.md +2 -2
  42. package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +160 -0
  43. package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +316 -0
  44. package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +672 -0
  45. package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +957 -0
  46. package/docs/academy/04-APIReferences/renown-sdk/_category_.json +8 -0
  47. package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +7 -39
  48. package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +72 -24
  49. package/docs/academy/08-Glossary.md +7 -0
  50. package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +171 -0
  51. package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +462 -0
  52. package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +422 -0
  53. package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +370 -0
  54. package/docusaurus.config.ts +28 -3
  55. package/package.json +1 -1
  56. package/sidebars.ts +49 -12
  57. package/src/css/custom.css +26 -18
  58. package/static/img/Vetra-logo-dark.svg +11 -0
  59. package/static/img/vetra-logo-light.svg +11 -0
  60. package/docs/academy/00-EthereumArgentinaHackathon.md +0 -207
  61. package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +0 -218
  62. package/docs/academy/01-GetStarted/06-ReactorMCP.md +0 -58
  63. package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +0 -65
  64. package/docs/academy/05-Architecture/04-MovingBeyondCRUD +0 -61
  65. /package/docs/academy/{01-GetStarted → 02-MasteryTrack/01-BuilderEnvironment}/images/Modules.png +0 -0
  66. /package/docs/academy/{01-GetStarted → 02-MasteryTrack/01-BuilderEnvironment}/images/VetraStudioDrive.png +0 -0
  67. /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline → 02-RevisionHistoryTimeline/360/237/232/247"} +0 -0
  68. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{01-WhatIsADocumentModel → 360/237/232/247 /01-WhatIsADocumentModel"} +0 -0
  69. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{02-DAOandDocumentsModelsQ+A → 360/237/232/247 /02-DAOandDocumentsModelsQ+A"} +0 -0
  70. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{02-domain-modeling → 360/237/232/247 /02-domain-modeling"} +0 -0
  71. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{03-BenefitsOfDocumentModels → 360/237/232/247 /03-BenefitsOfDocumentModels"} +0 -0
  72. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{04-UtilitiesAndTips → 360/237/232/247 /04-UtilitiesAndTips"} +0 -0
  73. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{05-best-practices → 360/237/232/247 /05-best-practices"} +0 -0
  74. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{_category_.json → 360/237/232/247 /_category_.json"} +0 -0
  75. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{three-data-layers.png → 360/237/232/247 /three-data-layers.png"} +0 -0
@@ -1,159 +1,268 @@
1
- # Implement Operation Reducers
1
+ # Implement the document model
2
2
 
3
- In this section, we will implement and test the operation reducers for the `ChatRoom` document model. In order to do this, you have to export the document model from the Connect application and import it into your powerhouse project directory.
3
+ :::tip Tutorial Repository
4
+ 📦 **Reference Code**: [chatroom-demo](https://github.com/powerhouse-inc/chatroom-demo)
4
5
 
5
- To export the document model, follow the steps in the `Define Chatroom Document Model` section.
6
+ This tutorial covers two key implementations:
7
+ 1. **Reducers**: Implementing the reducer logic for all ChatRoom operations
8
+ 2. **Tests**: Writing comprehensive tests for the reducers
6
9
 
7
- ## Import Document Model and Generate Code
10
+ You can view the exact implementation in the repository's `document-models/chat-room/src/` directory.
11
+ :::
8
12
 
9
- To import the document model into your powerhouse project, you can either:
13
+ <details>
14
+ <summary>📖 How to use this tutorial</summary>
10
15
 
11
- - Copy&Paste the file directly into the root of your powerhouse project.
12
- - Or drag&drop the file into the powerhouse project directory in the VSCode editor as seen in the image below:
16
+ This tutorial covers implementing reducers and tests.
13
17
 
14
- Either step will import the document model into your powerhouse project.
18
+ ### Compare your reducer implementation
19
+
20
+ After implementing your reducers:
21
+
22
+ ```bash
23
+ # Compare your reducers with the reference
24
+ git diff tutorial/main -- document-models/chat-room/src/reducers/
25
+
26
+ # View the reference reducer implementation
27
+ git show tutorial/main:document-models/chat-room/src/reducers/messages.ts
28
+ ```
29
+
30
+ ### Compare your tests
31
+
32
+ After writing tests:
33
+
34
+ ```bash
35
+ # Compare your tests with the reference
36
+ git diff tutorial/main -- document-models/chat-room/src/tests/
37
+
38
+ # View the reference test implementation
39
+ git show tutorial/main:document-models/chat-room/src/tests/messages.test.ts
40
+ ```
41
+
42
+ ### Visual comparison with GitHub Desktop
43
+
44
+ After committing your work, compare visually:
45
+ 1. **Branch** menu → **"Compare to Branch..."**
46
+ 2. Select `tutorial/main`
47
+ 3. Review differences in the visual interface
48
+
49
+ See step 1 for detailed GitHub Desktop instructions.
50
+
51
+ ### If you get stuck
52
+
53
+ View or reset to the reference:
54
+
55
+ ```bash
56
+ # View the reducer code from the reference
57
+ git show tutorial/main:document-models/chat-room/src/reducers/messages.ts
58
+
59
+ # Reset to reference (WARNING: loses your changes)
60
+ git reset --hard tutorial/main
61
+ ```
62
+
63
+ </details>
64
+
65
+ In this section, we will implement and test the operation reducers for the **ChatRoom** document model. For this, you have to export the document model specification from the Connect application and import it into your Powerhouse project directory.
66
+
67
+ To export the document model specification, follow the steps in the [Define ChatRoom Document Model](/academy/ExampleUsecases/Chatroom/DefineChatroomDocumentModel) section.
68
+
69
+ ## Understanding reducers in document models
70
+
71
+ Reducers are a core concept in Powerhouse document models. They implement the state transition logic for each operation defined in your schema.
72
+
73
+ :::info
74
+ **Connection to schema definition language (SDL)**: The reducers directly implement the operations you defined in your SDL. Remember how we defined `AddMessageInput`, `AddEmojiReactionInput`, `RemoveEmojiReactionInput`, `EditChatNameInput`, and `EditChatDescriptionInput` in our schema?
75
+ The reducers provide the actual implementation of what happens when those operations are performed.
76
+ :::
77
+
78
+ To import the document model specification into your Powerhouse project, you can either:
79
+
80
+ - Copy and paste the file directly into the root of your Powerhouse project.
81
+ - Or drag and drop the file into the Powerhouse project directory in the VSCode editor as seen in the image below:
82
+
83
+ Either step will import the document model specification into your Powerhouse project.
15
84
 
16
85
  ![vscode image](image-4.png)
17
86
 
18
- The next steps will take place in the VSCode editor. Make sure to have it open and the terminal window inside vscode open as well.
87
+ ## In your project directory
88
+
89
+ The next steps will take place in the VSCode editor. Make sure to have it open and the terminal window inside VSCode open as well.
19
90
 
20
- To write the opearation reducers of the `ChatRoom` document model, you need to generate the document model code from the document model file you have exported into the powerhouse project directory.
91
+ To write the operation reducers of the **ChatRoom** document model, you need to generate the document model code from the document model specification file you have exported into the Powerhouse project directory.
21
92
 
22
93
  To do this, run the following command in the terminal:
23
94
 
24
95
  ```bash
25
- ph generate ChatRoom.phdm.phd
96
+ ph generate ChatRoom.phd
26
97
  ```
27
98
 
28
- You will see that this action created a range of files for you. Before diving in we'll look at this simple schema to make you familiar with the structure you've defined in the document model once more. It shows how each type is connected to the next one.
99
+ You will see that this action created a range of files for you. Before diving in, let's look at this simple schema to familiarize yourself with the structure you've defined in the document model once more. It shows how each type is connected to the next one.
29
100
 
30
101
  ![Chatroom-demo Schema](image.png)
31
102
 
32
- Now you can navigate to `/document-models/chat-room/src/reducers/general-operations.ts` and start writing the operation reducers.
103
+ Now you can navigate to `/document-models/chat-room/src/reducers/messages.ts` and start writing the operation reducers.
33
104
 
34
- Open the `general-operations.ts` file and you should see the code that needs to be filled for the five operations you have defined earlier. Image below shows the code that needs to be filled:
105
+ Open the `messages.ts` file and you should see the scaffolding code that needs to be filled for the five operations you have specified earlier. The generated file will look like this:
35
106
 
36
- ![chatroom ts file](image-5.png)
107
+ ```typescript
108
+ import type { ChatRoomMessagesOperations } from "chatroom/document-models/chat-room";
37
109
 
38
- ## Write the Operation Reducers
110
+ export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
111
+ addMessageOperation(state, action) {
112
+ // TODO: Implement "addMessageOperation" reducer
113
+ throw new Error('Reducer "addMessageOperation" not yet implemented');
114
+ },
115
+ senderOperation(state, action) {
116
+ // TODO: Implement "senderOperation" reducer
117
+ throw new Error('Reducer "senderOperation" not yet implemented');
118
+ },
119
+ addEmojiReactionOperation(state, action) {
120
+ // TODO: Implement "addEmojiReactionOperation" reducer
121
+ throw new Error('Reducer "addEmojiReactionOperation" not yet implemented');
122
+ },
123
+ removeEmojiReactionOperation(state, action) {
124
+ // TODO: Implement "removeEmojiReactionOperation" reducer
125
+ throw new Error('Reducer "removeEmojiReactionOperation" not yet implemented');
126
+ }
127
+ };
128
+ ```
39
129
 
40
- 1. Copy&paste the code below into the `general-operations.ts` file in the `reducers` folder.
41
- 2. Save the `general-operations.ts` file.
130
+ ## Write the operation reducers
42
131
 
43
- ```typescript
44
- /**
45
- * This is a scaffold file meant for customization:
46
- * - modify it by implementing the reducer functions
47
- * - delete the file and run the code generator again to have it reset
48
- */
132
+ 1. Copy and paste the code below into the `messages.ts` file in the `reducers` folder.
133
+ 2. Save the file.
49
134
 
50
- import { MessageContentCannotBeEmpty } from "../../gen/general-operations/error.js";
51
- import type { ChatRoomGeneralOperationsOperations } from "chatroom/document-models/chat-room";
135
+ <details>
136
+ <summary>Operation Reducers</summary>
52
137
 
53
- export const chatRoomGeneralOperationsOperations: ChatRoomGeneralOperationsOperations =
54
- {
55
- addMessageOperation(state, action) {
56
- if (action.input.content === "") {
57
- throw new MessageContentCannotBeEmpty();
58
- }
138
+ ```typescript
139
+ import {
140
+ MessageNotFoundError,
141
+ MessageContentCannotBeEmptyError,
142
+ } from "../../gen/messages/error.js";
143
+ import type { ChatRoomMessagesOperations } from "chatroom-package/document-models/chat-room";
144
+
145
+ export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
146
+ addMessageOperation(state, action) {
147
+ if (action.input.content === "") {
148
+ throw new MessageContentCannotBeEmptyError();
149
+ }
150
+
151
+ const newMessage = {
152
+ id: action.input.messageId,
153
+ sender: {
154
+ id: action.input.sender.id,
155
+ name: action.input.sender.name || null,
156
+ avatarUrl: action.input.sender.avatarUrl || null,
157
+ },
158
+ content: action.input.content,
159
+ sentAt: action.input.sentAt,
160
+ reactions: [],
161
+ };
59
162
 
60
- const newMessage = {
61
- id: action.input.messageId,
62
- sender: {
63
- id: action.input.sender.id,
64
- name: action.input.sender.name || null,
65
- avatarUrl: action.input.sender.avatarUrl || null,
66
- },
67
- content: action.input.content,
68
- sentAt: action.input.sentAt,
69
- reactions: [],
70
- };
71
-
72
- state.messages.push(newMessage);
73
- },
74
- addEmojiReactionOperation(state, action) {
75
- const message = state.messages.find(
76
- (m) => m.id === action.input.messageId,
163
+ state.messages.push(newMessage);
164
+ },
165
+ addEmojiReactionOperation(state, action) {
166
+ const message = state.messages.find((m) => m.id === action.input.messageId);
167
+ if (!message) {
168
+ throw new MessageNotFoundError(
169
+ `Message with ID ${action.input.messageId} not found`,
77
170
  );
78
- if (!message) {
79
- return state;
80
- }
171
+ }
81
172
 
82
- if (!message.reactions) {
83
- message.reactions = [];
84
- }
173
+ if (!message.reactions) {
174
+ message.reactions = [];
175
+ }
85
176
 
86
- const existingReaction = message.reactions.find(
87
- (r) => r.type === action.input.type,
88
- );
177
+ const existingReaction = message.reactions.find(
178
+ (r) => r.type === action.input.type,
179
+ );
89
180
 
90
- if (existingReaction) {
91
- if (!existingReaction.reactedBy.includes(action.input.reactedBy)) {
92
- existingReaction.reactedBy.push(action.input.reactedBy);
93
- }
94
- } else {
95
- message.reactions.push({
96
- type: action.input.type,
97
- reactedBy: [action.input.reactedBy],
98
- });
181
+ if (existingReaction) {
182
+ if (!existingReaction.reactedBy.includes(action.input.reactedBy)) {
183
+ existingReaction.reactedBy.push(action.input.reactedBy);
99
184
  }
100
- },
101
- removeEmojiReactionOperation(state, action) {
102
- const message = state.messages.find(
103
- (m) => m.id === action.input.messageId,
185
+ } else {
186
+ message.reactions.push({
187
+ type: action.input.type,
188
+ reactedBy: [action.input.reactedBy],
189
+ });
190
+ }
191
+ },
192
+ removeEmojiReactionOperation(state, action) {
193
+ const message = state.messages.find((m) => m.id === action.input.messageId);
194
+ if (!message) {
195
+ throw new MessageNotFoundError(
196
+ `Message with ID ${action.input.messageId} not found`,
104
197
  );
105
- if (!message) {
106
- return state;
107
- }
198
+ }
108
199
 
109
- if (!message.reactions) {
110
- return;
111
- }
200
+ if (!message.reactions) {
201
+ return;
202
+ }
112
203
 
113
- const reactionIndex = message.reactions.findIndex(
114
- (r) => r.type === action.input.type,
115
- );
116
- if (reactionIndex === -1) {
117
- return;
118
- }
204
+ const reactionIndex = message.reactions.findIndex(
205
+ (r) => r.type === action.input.type,
206
+ );
207
+ if (reactionIndex === -1) {
208
+ return;
209
+ }
119
210
 
120
- const reaction = message.reactions[reactionIndex];
121
- const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
211
+ const reaction = message.reactions[reactionIndex];
212
+ const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
122
213
 
123
- if (userIndex !== -1) {
124
- reaction.reactedBy.splice(userIndex, 1);
214
+ if (userIndex !== -1) {
215
+ reaction.reactedBy.splice(userIndex, 1);
125
216
 
126
- if (reaction.reactedBy.length === 0) {
127
- message.reactions.splice(reactionIndex, 1);
128
- }
217
+ if (reaction.reactedBy.length === 0) {
218
+ message.reactions.splice(reactionIndex, 1);
129
219
  }
130
- },
131
- editChatNameOperation(state, action) {
132
- state.name = action.input.name || "";
133
- },
134
- editChatDescriptionOperation(state, action) {
135
- state.description = action.input.description || "";
136
- },
137
- };
220
+ }
221
+ },
222
+ };
223
+ ```
224
+ </details>
225
+
226
+
227
+ 1. Do the same for the reducers of our "settings" operations. Copy and paste the code below into the `settings.ts` file in the `reducers` folder.
228
+ 2. Save the file.
138
229
 
230
+
231
+ <details>
232
+ <summary>Operation Reducers</summary>
233
+
234
+ ```typescript
235
+ import type { ChatRoomSettingsOperations } from "chatroom-package/document-models/chat-room";
236
+
237
+ export const chatRoomSettingsOperations: ChatRoomSettingsOperations = {
238
+ editChatNameOperation(state, action) {
239
+ state.name = action.input.name || "";
240
+ },
241
+ editChatDescriptionOperation(state, action) {
242
+ state.description = action.input.description || "";
243
+ },
244
+ };
139
245
  ```
140
246
 
141
- ## Write the Operation Reducers Tests
247
+ </details>
142
248
 
143
- In order to make sure the operation reducers are working as expected before implementing an editor interface, you need to write tests for them.
144
249
 
145
- The auto generated test will only validate if an action or message in our case is included but will not verify if the reducer mutation is succesfull. This is the type of test you'll have to write as a developer.
250
+ ## Write the operation reducer tests
146
251
 
147
- Navigate to `/document-models/chat-room/src/tests/general-operations.test.ts` and copy&paste the code below into the file. Save the file.
252
+ In order to make sure the operation reducers are working as expected, you need to write tests for them.
148
253
 
149
- Here are the tests for the five operations written in the reducers file.
254
+ Navigate to `/document-models/chat-room/src/tests` and replace the scaffolding code with comprehensive tests.
150
255
 
151
- ```typescript
152
- /**
153
- * This is a scaffold file meant for customization:
154
- * - change it by adding new tests or modifying the existing ones
155
- */
256
+ Here are the tests for the five operations implemented in the reducers file. The test file:
257
+ - Uses Vitest for testing
258
+ - Creates an empty ChatRoom document model
259
+ - Tests add message, add/remove reactions, and edit operations
260
+ - Verifies both the operation history and the resulting state
261
+
262
+ <details>
263
+ <summary>Operation Reducer Tests</summary>
156
264
 
265
+ ```typescript
157
266
  import { describe, it, expect, beforeEach } from "vitest";
158
267
  import { generateId } from "document-model/core";
159
268
  import {
@@ -181,8 +290,8 @@ describe("GeneralOperations Operations", () => {
181
290
  document = utils.createDocument();
182
291
  });
183
292
 
293
+ // Helper function to add a message for testing
184
294
  const addMessageHelper = (): [ChatRoomDocument, AddMessageInput] => {
185
- // This is a helper function for our upcoming tests
186
295
  const input: AddMessageInput = {
187
296
  content: "Hello, World!",
188
297
  messageId: generateId(),
@@ -199,30 +308,29 @@ describe("GeneralOperations Operations", () => {
199
308
  return [updatedDocument, input];
200
309
  };
201
310
 
311
+ // Test adding a new message
202
312
  it("should handle addMessage operation", () => {
203
313
  const [updatedDocument, input] = addMessageHelper();
204
314
 
205
- expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the message is being added to the operations history
206
- expect(updatedDocument.operations.global[0].action.type).toBe(
207
- "ADD_MESSAGE",
208
- );
209
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
210
- input,
211
- );
315
+ // Verify the operation was recorded
316
+ expect(updatedDocument.operations.global).toHaveLength(1);
317
+ expect(updatedDocument.operations.global[0].action.type).toBe("ADD_MESSAGE");
318
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
212
319
  expect(updatedDocument.operations.global[0].index).toEqual(0);
213
320
 
214
- expect(updatedDocument.state.global.messages).toHaveLength(1); // We're validating that the message is present in the message state of the document
321
+ // Verify the message was added to state
322
+ expect(updatedDocument.state.global.messages).toHaveLength(1);
215
323
  expect(updatedDocument.state.global.messages[0]).toMatchObject({
216
324
  id: input.messageId,
217
325
  content: input.content,
218
326
  sender: input.sender,
219
327
  sentAt: input.sentAt,
220
- reactions: [], // We also want to make sure that reaction object is an empty array
328
+ reactions: [],
221
329
  });
222
330
  });
223
331
 
332
+ // Test adding an emoji reaction
224
333
  it("should handle addEmojiReaction operation", () => {
225
- // We're validating that we can react using an emoji with a helper function
226
334
  const [doc, addMessageInput] = addMessageHelper();
227
335
 
228
336
  let updatedDocument = doc;
@@ -238,26 +346,24 @@ describe("GeneralOperations Operations", () => {
238
346
  addEmojiReaction(addEmojiReactionInput),
239
347
  );
240
348
 
241
- expect(updatedDocument.operations.global).toHaveLength(2); // We're validating that the emoji reaction is added to the operation history of the doc.
242
- expect(updatedDocument.operations.global[1].action.type).toBe(
243
- "ADD_EMOJI_REACTION",
244
- );
349
+ // Verify the operation was recorded
350
+ expect(updatedDocument.operations.global).toHaveLength(2);
351
+ expect(updatedDocument.operations.global[1].action.type).toBe("ADD_EMOJI_REACTION");
245
352
  expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
246
353
  addEmojiReactionInput,
247
354
  );
248
355
  expect(updatedDocument.operations.global[1].index).toEqual(1);
249
356
 
250
- expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(1); // We're validating that the message we created has only one reaction
251
- expect(
252
- updatedDocument.state.global.messages[0].reactions?.[0],
253
- ).toMatchObject({
254
- reactedBy: [addEmojiReactionInput.reactedBy], // We're validating that reactedBy object only contains the right address
357
+ // Verify the reaction was added
358
+ expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(1);
359
+ expect(updatedDocument.state.global.messages[0].reactions?.[0]).toMatchObject({
360
+ reactedBy: [addEmojiReactionInput.reactedBy],
255
361
  type: addEmojiReactionInput.type,
256
362
  });
257
363
  });
258
364
 
365
+ // Test adding reaction to non-existing message
259
366
  it("should handle addEmojiReaction operation to a non existing message", () => {
260
- // We're testing that the state doesn't change when reacting to a non-existing message
261
367
  const input: AddEmojiReactionInput = {
262
368
  messageId: "invalid-message-id",
263
369
  reactedBy: "anon-user",
@@ -267,14 +373,12 @@ describe("GeneralOperations Operations", () => {
267
373
  const updatedDocument = reducer(document, addEmojiReaction(input));
268
374
 
269
375
  expect(updatedDocument.operations.global).toHaveLength(1);
270
- expect(updatedDocument.operations.global[0].action.type).toBe(
271
- "ADD_EMOJI_REACTION",
272
- );
376
+ expect(updatedDocument.operations.global[0].action.type).toBe("ADD_EMOJI_REACTION");
273
377
  expect(updatedDocument.state.global.messages).toHaveLength(0);
274
378
  });
275
379
 
380
+ // Test removing an emoji reaction
276
381
  it("should handle removeEmojiReaction operation", () => {
277
- // We're making use of a helper function to check if we can remove an EmojiReaction
278
382
  const [doc, addMessageInput] = addMessageHelper();
279
383
 
280
384
  let updatedDocument = doc;
@@ -291,7 +395,6 @@ describe("GeneralOperations Operations", () => {
291
395
  );
292
396
 
293
397
  const input: RemoveEmojiReactionInput = {
294
- // We're validating the removal of a message by our anon-user with a specific messageId
295
398
  messageId: addMessageInput.messageId,
296
399
  senderId: "anon-user",
297
400
  type: "THUMBS_UP",
@@ -299,19 +402,17 @@ describe("GeneralOperations Operations", () => {
299
402
 
300
403
  updatedDocument = reducer(updatedDocument, removeEmojiReaction(input));
301
404
 
302
- expect(updatedDocument.operations.global).toHaveLength(3); // We're validating that the operation was added to the operation history.
303
- expect(updatedDocument.operations.global[2].action.type).toBe(
304
- "REMOVE_EMOJI_REACTION",
305
- );
306
- expect(updatedDocument.operations.global[2].action.input).toStrictEqual(
307
- input,
308
- );
405
+ // Verify the operation was recorded
406
+ expect(updatedDocument.operations.global).toHaveLength(3);
407
+ expect(updatedDocument.operations.global[2].action.type).toBe("REMOVE_EMOJI_REACTION");
408
+ expect(updatedDocument.operations.global[2].action.input).toStrictEqual(input);
309
409
  expect(updatedDocument.operations.global[2].index).toEqual(2);
310
410
 
311
- // When the last user removes their reaction, the entire reaction should be removed
411
+ // Verify the reaction was removed
312
412
  expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(0);
313
413
  });
314
414
 
415
+ // Test editing chat name
315
416
  it("should handle editChatName operation", () => {
316
417
  const input: EditChatNameInput = {
317
418
  name: "New Chat Name",
@@ -319,18 +420,17 @@ describe("GeneralOperations Operations", () => {
319
420
 
320
421
  const updatedDocument = reducer(document, editChatName(input));
321
422
 
322
- expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the operation is added to the operations history
323
- expect(updatedDocument.operations.global[0].action.type).toBe(
324
- "EDIT_CHAT_NAME",
325
- );
326
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
327
- input,
328
- );
423
+ // Verify the operation was recorded
424
+ expect(updatedDocument.operations.global).toHaveLength(1);
425
+ expect(updatedDocument.operations.global[0].action.type).toBe("EDIT_CHAT_NAME");
426
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
329
427
  expect(updatedDocument.operations.global[0].index).toEqual(0);
330
428
 
429
+ // Verify the name was updated
331
430
  expect(updatedDocument.state.global.name).toBe(input.name);
332
431
  });
333
432
 
433
+ // Test editing chat description
334
434
  it("should handle editChatDescription operation", () => {
335
435
  const input: EditChatDescriptionInput = {
336
436
  description: "New Chat Description",
@@ -338,34 +438,55 @@ describe("GeneralOperations Operations", () => {
338
438
 
339
439
  const updatedDocument = reducer(document, editChatDescription(input));
340
440
 
341
- expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the operation is added to the operations history
342
- expect(updatedDocument.operations.global[0].action.type).toBe(
343
- "EDIT_CHAT_DESCRIPTION",
344
- );
345
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
346
- input,
347
- );
441
+ // Verify the operation was recorded
442
+ expect(updatedDocument.operations.global).toHaveLength(1);
443
+ expect(updatedDocument.operations.global[0].action.type).toBe("EDIT_CHAT_DESCRIPTION");
444
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
348
445
  expect(updatedDocument.operations.global[0].index).toEqual(0);
349
446
 
447
+ // Verify the description was updated
350
448
  expect(updatedDocument.state.global.description).toBe(input.description);
351
449
  });
352
450
  });
353
451
  ```
354
452
 
453
+ </details>
454
+
355
455
  Now you can run the tests to make sure the operation reducers are working as expected.
356
456
 
357
457
  ```bash
358
- npm run test
458
+ pnpm run test
359
459
  ```
360
460
 
361
461
  Output should be as follows:
362
462
 
363
463
  ```bash
464
+ ✓ document-models/chat-room/src/tests/document-model.test.ts (3 tests) 1ms
465
+ ✓ document-models/chat-room/src/tests/general-operations.test.ts (6 tests) 8ms
466
+
364
467
  Test Files 2 passed (2)
365
- Tests 7 passed (7)
468
+ Tests 9 passed (9)
366
469
  Start at 15:19:52
367
470
  Duration 3.61s (transform 77ms, setup 0ms, collect 3.50s, tests 14ms, environment 0ms, prepare 474ms)
368
471
  ```
369
472
 
370
- If you got the same output, you have successfully implemented the operation reducers and tests for the `ChatRoom` document model.
371
- Continue to the next section to learn how to implement the document model editor so you can see a simple user interface for the `ChatRoom` document model in action.
473
+ If you got the same output, you have successfully implemented the operation reducers and tests for the **ChatRoom** document model.
474
+
475
+ ## Compare with reference implementation
476
+
477
+ Verify your implementation matches the tutorial:
478
+
479
+ ```bash
480
+ # View reference reducer implementation
481
+ git show tutorial/main:document-models/chat-room/src/reducers/general-operations.ts
482
+
483
+ # View reference test implementation
484
+ git show tutorial/main:document-models/chat-room/src/tests/general-operations.test.ts
485
+
486
+ # Compare your entire implementation
487
+ git diff tutorial/main -- document-models/chat-room/src/
488
+ ```
489
+
490
+ ## Up next: ChatRoom editor
491
+
492
+ Continue to the next section to learn how to implement the document model editor so you can see a simple user interface for the **ChatRoom** document model in action.