@powerhousedao/academy 5.1.0-staging.0 → 5.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 (76) hide show
  1. package/CHANGELOG.md +46 -1148
  2. package/blog/BeyondCommunication-ABlueprintForDevelopment.md +1 -2
  3. package/blog/TheChallengeOfChange.md +0 -1
  4. package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
  5. package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +27 -24
  6. package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +10 -155
  7. package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +35 -122
  8. package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +155 -178
  9. package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
  10. package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +22 -62
  11. package/docs/academy/01-GetStarted/06-ReactorMCP.md +58 -0
  12. package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +1 -1
  13. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +2 -2
  14. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +44 -75
  15. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +22 -28
  16. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +31 -28
  17. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +206 -211
  18. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +62 -176
  19. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +0 -21
  20. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +319 -309
  21. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +0 -4
  22. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +0 -4
  23. package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
  24. package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +35 -111
  25. package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +79 -195
  26. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +241 -435
  27. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +27 -388
  28. package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +7 -95
  29. package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
  30. package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +2 -6
  31. package/docs/academy/04-APIReferences/01-ReactHooks.md +501 -291
  32. package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +39 -7
  33. package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +65 -0
  34. package/docs/academy/05-Architecture/04-MovingBeyondCRUD +61 -0
  35. package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +24 -72
  36. package/docs/academy/08-Glossary.md +0 -7
  37. package/docusaurus.config.ts +3 -28
  38. package/package.json +1 -1
  39. package/sidebars.ts +13 -49
  40. package/src/css/custom.css +18 -26
  41. package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -425
  42. package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -557
  43. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/Modules.png +0 -0
  44. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/VetraStudioDrive.png +0 -0
  45. package/docs/academy/02-MasteryTrack/05-Launch/05-DockerDeployment.md +0 -384
  46. package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +0 -24
  47. package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +0 -211
  48. package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  49. package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +0 -462
  50. package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +0 -45
  51. package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  52. package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +0 -61
  53. package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +0 -384
  54. package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +0 -8
  55. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +0 -7
  56. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +0 -9
  57. package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +0 -160
  58. package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +0 -316
  59. package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +0 -672
  60. package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +0 -957
  61. package/docs/academy/04-APIReferences/renown-sdk/_category_.json +0 -8
  62. package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  63. package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +0 -462
  64. package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  65. package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +0 -370
  66. package/static/img/Vetra-logo-dark.svg +0 -11
  67. package/static/img/vetra-logo-light.svg +0 -11
  68. /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline/360/237/232/247" → 02-RevisionHistoryTimeline} +0 -0
  69. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /01-WhatIsADocumentModel" → 01-WhatIsADocumentModel} +0 -0
  70. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-DAOandDocumentsModelsQ+A" → 02-DAOandDocumentsModelsQ+A} +0 -0
  71. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-domain-modeling" → 02-domain-modeling} +0 -0
  72. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /03-BenefitsOfDocumentModels" → 03-BenefitsOfDocumentModels} +0 -0
  73. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /04-UtilitiesAndTips" → 04-UtilitiesAndTips} +0 -0
  74. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /05-best-practices" → 05-best-practices} +0 -0
  75. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /_category_.json" → _category_.json} +0 -0
  76. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /three-data-layers.png" → three-data-layers.png} +0 -0
@@ -1,262 +1,152 @@
1
- # Implement the document model
1
+ # Implement Operation Reducers
2
2
 
3
- :::tip Tutorial Repository
4
- 📦 **Reference Code**: [chatroom-demo](https://github.com/powerhouse-inc/chatroom-demo)
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.
5
4
 
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
5
+ To export the document model, follow the steps in the `Define Chatroom Document Model` section.
9
6
 
10
- You can view the exact implementation in the repository's `document-models/chat-room/src/` directory.
11
- :::
7
+ ## Import Document Model and Generate Code
12
8
 
13
- <details>
14
- <summary>📖 How to use this tutorial</summary>
9
+ To import the document model into your powerhouse project, you can either:
15
10
 
16
- This tutorial covers implementing reducers and tests.
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:
17
13
 
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.
14
+ Either step will import the document model into your powerhouse project.
84
15
 
85
16
  ![vscode image](image-4.png)
86
17
 
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.
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.
90
19
 
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.
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.
92
21
 
93
22
  To do this, run the following command in the terminal:
94
23
 
95
24
  ```bash
96
- ph generate ChatRoom.phd
25
+ ph generate ChatRoom.phdm.phd
97
26
  ```
98
27
 
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.
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.
100
29
 
101
30
  ![Chatroom-demo Schema](image.png)
102
31
 
103
- ## Implement the messages reducers
32
+ Now you can navigate to `/document-models/chat-room/src/reducers/general-operations.ts` and start writing the operation reducers.
104
33
 
105
- Navigate to `/document-models/chat-room/src/reducers/messages.ts` and start writing the operation reducers for the messages module.
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:
106
35
 
107
- Open the `messages.ts` file and you should see the scaffolding code that needs to be filled for the three message operations. The generated file will look like this:
36
+ ![chatroom ts file](image-5.png)
108
37
 
109
- ```typescript
110
- import type { ChatRoomMessagesOperations } from "@powerhousedao/chatroom-package/document-models/chat-room";
111
-
112
- export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
113
- addMessageOperation(state, action) {
114
- // TODO: Implement "addMessageOperation" reducer
115
- throw new Error('Reducer "addMessageOperation" not yet implemented');
116
- },
117
- addEmojiReactionOperation(state, action) {
118
- // TODO: Implement "addEmojiReactionOperation" reducer
119
- throw new Error('Reducer "addEmojiReactionOperation" not yet implemented');
120
- },
121
- removeEmojiReactionOperation(state, action) {
122
- // TODO: Implement "removeEmojiReactionOperation" reducer
123
- throw new Error('Reducer "removeEmojiReactionOperation" not yet implemented');
124
- }
125
- };
126
- ```
38
+ ## Write the Operation Reducers
127
39
 
128
- ### Write the messages operation reducers
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.
129
42
 
130
- Copy and paste the code below into the `messages.ts` file in the `reducers` folder, replacing the scaffolding code:
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
+ */
131
49
 
132
- <details>
133
- <summary>Messages Operation Reducers</summary>
50
+ import { MessageContentCannotBeEmpty } from "../../gen/general-operations/error.js";
51
+ import type { ChatRoomGeneralOperationsOperations } from "chatroom/document-models/chat-room";
134
52
 
135
- ```typescript
136
- import {
137
- MessageNotFoundError,
138
- MessageContentCannotBeEmptyError,
139
- } from "../../gen/messages/error.js";
140
- import type { ChatRoomMessagesOperations } from "@powerhousedao/chatroom-package/document-models/chat-room";
141
-
142
- export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
143
- addMessageOperation(state, action) {
144
- if (action.input.content === "") {
145
- throw new MessageContentCannotBeEmptyError();
146
- }
147
-
148
- const newMessage = {
149
- id: action.input.messageId,
150
- sender: {
151
- id: action.input.sender.id,
152
- name: action.input.sender.name || null,
153
- avatarUrl: action.input.sender.avatarUrl || null,
154
- },
155
- content: action.input.content,
156
- sentAt: action.input.sentAt,
157
- reactions: [],
158
- };
53
+ export const chatRoomGeneralOperationsOperations: ChatRoomGeneralOperationsOperations =
54
+ {
55
+ addMessageOperation(state, action) {
56
+ if (action.input.content === "") {
57
+ throw new MessageContentCannotBeEmpty();
58
+ }
159
59
 
160
- state.messages.push(newMessage);
161
- },
162
- addEmojiReactionOperation(state, action) {
163
- const message = state.messages.find((m) => m.id === action.input.messageId);
164
- if (!message) {
165
- throw new MessageNotFoundError(
166
- `Message with ID ${action.input.messageId} not found`,
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,
167
77
  );
168
- }
78
+ if (!message) {
79
+ return state;
80
+ }
169
81
 
170
- if (!message.reactions) {
171
- message.reactions = [];
172
- }
82
+ if (!message.reactions) {
83
+ message.reactions = [];
84
+ }
173
85
 
174
- const existingReaction = message.reactions.find(
175
- (r) => r.type === action.input.type,
176
- );
86
+ const existingReaction = message.reactions.find(
87
+ (r) => r.type === action.input.type,
88
+ );
177
89
 
178
- if (existingReaction) {
179
- if (!existingReaction.reactedBy.includes(action.input.reactedBy)) {
180
- existingReaction.reactedBy.push(action.input.reactedBy);
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
99
  }
182
- } else {
183
- message.reactions.push({
184
- type: action.input.type,
185
- reactedBy: [action.input.reactedBy],
186
- });
187
- }
188
- },
189
- removeEmojiReactionOperation(state, action) {
190
- const message = state.messages.find((m) => m.id === action.input.messageId);
191
- if (!message) {
192
- throw new MessageNotFoundError(
193
- `Message with ID ${action.input.messageId} not found`,
100
+ },
101
+ removeEmojiReactionOperation(state, action) {
102
+ const message = state.messages.find(
103
+ (m) => m.id === action.input.messageId,
194
104
  );
195
- }
196
-
197
- if (!message.reactions) {
198
- return;
199
- }
200
-
201
- const reactionIndex = message.reactions.findIndex(
202
- (r) => r.type === action.input.type,
203
- );
204
- if (reactionIndex === -1) {
205
- return;
206
- }
207
-
208
- const reaction = message.reactions[reactionIndex];
209
- const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
210
-
211
- if (userIndex !== -1) {
212
- reaction.reactedBy.splice(userIndex, 1);
213
-
214
- if (reaction.reactedBy.length === 0) {
215
- message.reactions.splice(reactionIndex, 1);
105
+ if (!message) {
106
+ return state;
216
107
  }
217
- }
218
- },
219
- };
220
- ```
221
108
 
222
- </details>
109
+ if (!message.reactions) {
110
+ return;
111
+ }
223
112
 
224
- ## Implement the settings reducers
113
+ const reactionIndex = message.reactions.findIndex(
114
+ (r) => r.type === action.input.type,
115
+ );
116
+ if (reactionIndex === -1) {
117
+ return;
118
+ }
225
119
 
226
- Navigate to `/document-models/chat-room/src/reducers/settings.ts` and implement the settings operation reducers.
120
+ const reaction = message.reactions[reactionIndex];
121
+ const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
227
122
 
228
- Copy and paste the code below into the `settings.ts` file in the `reducers` folder:
123
+ if (userIndex !== -1) {
124
+ reaction.reactedBy.splice(userIndex, 1);
229
125
 
230
- <details>
231
- <summary>Settings Operation Reducers</summary>
126
+ if (reaction.reactedBy.length === 0) {
127
+ message.reactions.splice(reactionIndex, 1);
128
+ }
129
+ }
130
+ },
131
+ editChatNameOperation(state, action) {
132
+ state.name = action.input.name || "";
133
+ },
134
+ editChatDescriptionOperation(state, action) {
135
+ state.description = action.input.description || "";
136
+ },
137
+ };
232
138
 
233
- ```typescript
234
- import type { ChatRoomSettingsOperations } from "@powerhousedao/chatroom-package/document-models/chat-room";
235
-
236
- export const chatRoomSettingsOperations: ChatRoomSettingsOperations = {
237
- editChatNameOperation(state, action) {
238
- state.name = action.input.name || "";
239
- },
240
- editChatDescriptionOperation(state, action) {
241
- state.description = action.input.description || "";
242
- },
243
- };
244
139
  ```
245
140
 
246
- </details>
247
-
248
- ## Write the operation reducer tests
249
-
250
- In order to make sure the operation reducers are working as expected, you need to write tests for them.
141
+ ## Write the Operation Reducers Tests
251
142
 
252
- Navigate to `/document-models/chat-room/src/tests` and you'll find test files for each module. Replace the scaffolding code with the tests below.
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.
253
144
 
254
- ### Messages operation tests
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.
255
146
 
256
- Replace the content of `messages.test.ts` with:
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.
257
148
 
258
- <details>
259
- <summary>Messages Operation Tests</summary>
149
+ Here are the tests for the five operations written in the reducers file.
260
150
 
261
151
  ```typescript
262
152
  /**
@@ -264,29 +154,55 @@ Replace the content of `messages.test.ts` with:
264
154
  * - change it by adding new tests or modifying the existing ones
265
155
  */
266
156
 
267
- import { describe, it, expect } from "vitest";
268
- import { generateMock } from "@powerhousedao/codegen";
157
+ import { describe, it, expect, beforeEach } from "vitest";
158
+ import { generateId } from "document-model/core";
269
159
  import {
270
160
  reducer,
271
161
  utils,
272
- isChatRoomDocument,
273
162
  addMessage,
274
- AddMessageInputSchema,
275
163
  addEmojiReaction,
276
- AddEmojiReactionInputSchema,
277
164
  removeEmojiReaction,
278
- RemoveEmojiReactionInputSchema,
279
- } from "@powerhousedao/chatroom-package/document-models/chat-room";
165
+ editChatName,
166
+ editChatDescription,
167
+ } from "../../gen/index.js";
168
+ import type {
169
+ ChatRoomDocument,
170
+ AddMessageInput,
171
+ AddEmojiReactionInput,
172
+ RemoveEmojiReactionInput,
173
+ EditChatNameInput,
174
+ EditChatDescriptionInput,
175
+ } from "../../gen/types.js";
176
+
177
+ describe("GeneralOperations Operations", () => {
178
+ let document: ChatRoomDocument;
179
+
180
+ beforeEach(() => {
181
+ document = utils.createDocument();
182
+ });
280
183
 
281
- describe("Messages Operations", () => {
282
- it("should handle addMessage operation", () => {
283
- const document = utils.createDocument();
284
- const input = generateMock(AddMessageInputSchema());
184
+ const addMessageHelper = (): [ChatRoomDocument, AddMessageInput] => {
185
+ // This is a helper function for our upcoming tests
186
+ const input: AddMessageInput = {
187
+ content: "Hello, World!",
188
+ messageId: generateId(),
189
+ sender: {
190
+ id: "anon-user",
191
+ name: null,
192
+ avatarUrl: null,
193
+ },
194
+ sentAt: new Date().toISOString(),
195
+ };
285
196
 
286
197
  const updatedDocument = reducer(document, addMessage(input));
287
198
 
288
- expect(isChatRoomDocument(updatedDocument)).toBe(true);
289
- expect(updatedDocument.operations.global).toHaveLength(1);
199
+ return [updatedDocument, input];
200
+ };
201
+
202
+ it("should handle addMessage operation", () => {
203
+ const [updatedDocument, input] = addMessageHelper();
204
+
205
+ expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the message is being added to the operations history
290
206
  expect(updatedDocument.operations.global[0].action.type).toBe(
291
207
  "ADD_MESSAGE",
292
208
  );
@@ -294,78 +210,116 @@ describe("Messages Operations", () => {
294
210
  input,
295
211
  );
296
212
  expect(updatedDocument.operations.global[0].index).toEqual(0);
213
+
214
+ expect(updatedDocument.state.global.messages).toHaveLength(1); // We're validating that the message is present in the message state of the document
215
+ expect(updatedDocument.state.global.messages[0]).toMatchObject({
216
+ id: input.messageId,
217
+ content: input.content,
218
+ sender: input.sender,
219
+ sentAt: input.sentAt,
220
+ reactions: [], // We also want to make sure that reaction object is an empty array
221
+ });
297
222
  });
223
+
298
224
  it("should handle addEmojiReaction operation", () => {
299
- const document = utils.createDocument();
300
- const input = generateMock(AddEmojiReactionInputSchema());
225
+ // We're validating that we can react using an emoji with a helper function
226
+ const [doc, addMessageInput] = addMessageHelper();
301
227
 
302
- const updatedDocument = reducer(document, addEmojiReaction(input));
228
+ let updatedDocument = doc;
303
229
 
304
- expect(isChatRoomDocument(updatedDocument)).toBe(true);
305
- expect(updatedDocument.operations.global).toHaveLength(1);
306
- expect(updatedDocument.operations.global[0].action.type).toBe(
230
+ const addEmojiReactionInput: AddEmojiReactionInput = {
231
+ messageId: addMessageInput.messageId,
232
+ reactedBy: "anon-user",
233
+ type: "THUMBS_UP",
234
+ };
235
+
236
+ updatedDocument = reducer(
237
+ updatedDocument,
238
+ addEmojiReaction(addEmojiReactionInput),
239
+ );
240
+
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(
307
243
  "ADD_EMOJI_REACTION",
308
244
  );
309
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
310
- input,
245
+ expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
246
+ addEmojiReactionInput,
311
247
  );
312
- expect(updatedDocument.operations.global[0].index).toEqual(0);
248
+ expect(updatedDocument.operations.global[1].index).toEqual(1);
249
+
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
255
+ type: addEmojiReactionInput.type,
256
+ });
313
257
  });
314
- it("should handle removeEmojiReaction operation", () => {
315
- const document = utils.createDocument();
316
- const input = generateMock(RemoveEmojiReactionInputSchema());
317
258
 
318
- const updatedDocument = reducer(document, removeEmojiReaction(input));
259
+ 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
+ const input: AddEmojiReactionInput = {
262
+ messageId: "invalid-message-id",
263
+ reactedBy: "anon-user",
264
+ type: "THUMBS_UP",
265
+ };
266
+
267
+ const updatedDocument = reducer(document, addEmojiReaction(input));
319
268
 
320
- expect(isChatRoomDocument(updatedDocument)).toBe(true);
321
269
  expect(updatedDocument.operations.global).toHaveLength(1);
322
270
  expect(updatedDocument.operations.global[0].action.type).toBe(
323
- "REMOVE_EMOJI_REACTION",
324
- );
325
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
326
- input,
271
+ "ADD_EMOJI_REACTION",
327
272
  );
328
- expect(updatedDocument.operations.global[0].index).toEqual(0);
273
+ expect(updatedDocument.state.global.messages).toHaveLength(0);
329
274
  });
330
- });
331
- ```
332
275
 
333
- </details>
276
+ it("should handle removeEmojiReaction operation", () => {
277
+ // We're making use of a helper function to check if we can remove an EmojiReaction
278
+ const [doc, addMessageInput] = addMessageHelper();
334
279
 
335
- ### Settings operation tests
280
+ let updatedDocument = doc;
336
281
 
337
- Replace the content of `settings.test.ts` with:
282
+ const addEmojiReactionInput: AddEmojiReactionInput = {
283
+ messageId: addMessageInput.messageId,
284
+ reactedBy: "anon-user",
285
+ type: "THUMBS_UP",
286
+ };
338
287
 
339
- <details>
340
- <summary>Settings Operation Tests</summary>
288
+ updatedDocument = reducer(
289
+ updatedDocument,
290
+ addEmojiReaction(addEmojiReactionInput),
291
+ );
341
292
 
342
- ```typescript
343
- /**
344
- * This is a scaffold file meant for customization:
345
- * - change it by adding new tests or modifying the existing ones
346
- */
293
+ const input: RemoveEmojiReactionInput = {
294
+ // We're validating the removal of a message by our anon-user with a specific messageId
295
+ messageId: addMessageInput.messageId,
296
+ senderId: "anon-user",
297
+ type: "THUMBS_UP",
298
+ };
347
299
 
348
- import { describe, it, expect } from "vitest";
349
- import { generateMock } from "@powerhousedao/codegen";
350
- import {
351
- reducer,
352
- utils,
353
- isChatRoomDocument,
354
- editChatName,
355
- EditChatNameInputSchema,
356
- editChatDescription,
357
- EditChatDescriptionInputSchema,
358
- } from "@powerhousedao/chatroom-package/document-models/chat-room";
300
+ updatedDocument = reducer(updatedDocument, removeEmojiReaction(input));
301
+
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
+ );
309
+ expect(updatedDocument.operations.global[2].index).toEqual(2);
310
+
311
+ // When the last user removes their reaction, the entire reaction should be removed
312
+ expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(0);
313
+ });
359
314
 
360
- describe("Settings Operations", () => {
361
315
  it("should handle editChatName operation", () => {
362
- const document = utils.createDocument();
363
- const input = generateMock(EditChatNameInputSchema());
316
+ const input: EditChatNameInput = {
317
+ name: "New Chat Name",
318
+ };
364
319
 
365
320
  const updatedDocument = reducer(document, editChatName(input));
366
321
 
367
- expect(isChatRoomDocument(updatedDocument)).toBe(true);
368
- expect(updatedDocument.operations.global).toHaveLength(1);
322
+ expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the operation is added to the operations history
369
323
  expect(updatedDocument.operations.global[0].action.type).toBe(
370
324
  "EDIT_CHAT_NAME",
371
325
  );
@@ -373,15 +327,18 @@ describe("Settings Operations", () => {
373
327
  input,
374
328
  );
375
329
  expect(updatedDocument.operations.global[0].index).toEqual(0);
330
+
331
+ expect(updatedDocument.state.global.name).toBe(input.name);
376
332
  });
333
+
377
334
  it("should handle editChatDescription operation", () => {
378
- const document = utils.createDocument();
379
- const input = generateMock(EditChatDescriptionInputSchema());
335
+ const input: EditChatDescriptionInput = {
336
+ description: "New Chat Description",
337
+ };
380
338
 
381
339
  const updatedDocument = reducer(document, editChatDescription(input));
382
340
 
383
- expect(isChatRoomDocument(updatedDocument)).toBe(true);
384
- expect(updatedDocument.operations.global).toHaveLength(1);
341
+ expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the operation is added to the operations history
385
342
  expect(updatedDocument.operations.global[0].action.type).toBe(
386
343
  "EDIT_CHAT_DESCRIPTION",
387
344
  );
@@ -389,177 +346,26 @@ describe("Settings Operations", () => {
389
346
  input,
390
347
  );
391
348
  expect(updatedDocument.operations.global[0].index).toEqual(0);
392
- });
393
- });
394
- ```
395
-
396
- </details>
397
-
398
- ### Document model tests
399
-
400
- The `document-model.test.ts` file contains tests to verify the document model structure. Replace its content with:
401
-
402
- <details>
403
- <summary>Document Model Tests</summary>
404
349
 
405
- ```typescript
406
- /**
407
- * This is a scaffold file meant for customization:
408
- * - change it by adding new tests or modifying the existing ones
409
- */
410
-
411
- import { describe, it, expect } from "vitest";
412
- import {
413
- utils,
414
- initialGlobalState,
415
- initialLocalState,
416
- chatRoomDocumentType,
417
- isChatRoomDocument,
418
- assertIsChatRoomDocument,
419
- isChatRoomState,
420
- assertIsChatRoomState,
421
- } from "@powerhousedao/chatroom-package/document-models/chat-room";
422
- import { ZodError } from "zod";
423
-
424
- describe("ChatRoom Document Model", () => {
425
- it("should create a new ChatRoom document", () => {
426
- const document = utils.createDocument();
427
-
428
- expect(document).toBeDefined();
429
- expect(document.header.documentType).toBe(chatRoomDocumentType);
430
- });
431
-
432
- it("should create a new ChatRoom document with a valid initial state", () => {
433
- const document = utils.createDocument();
434
- expect(document.state.global).toStrictEqual(initialGlobalState);
435
- expect(document.state.local).toStrictEqual(initialLocalState);
436
- expect(isChatRoomDocument(document)).toBe(true);
437
- expect(isChatRoomState(document.state)).toBe(true);
438
- });
439
- it("should reject a document that is not a ChatRoom document", () => {
440
- const wrongDocumentType = utils.createDocument();
441
- wrongDocumentType.header.documentType = "the-wrong-thing-1234";
442
- try {
443
- expect(assertIsChatRoomDocument(wrongDocumentType)).toThrow();
444
- expect(isChatRoomDocument(wrongDocumentType)).toBe(false);
445
- } catch (error) {
446
- expect(error).toBeInstanceOf(ZodError);
447
- }
350
+ expect(updatedDocument.state.global.description).toBe(input.description);
448
351
  });
449
- const wrongState = utils.createDocument();
450
- // @ts-expect-error - we are testing the error case
451
- wrongState.state.global = {
452
- ...{ notWhat: "you want" },
453
- };
454
- try {
455
- expect(isChatRoomState(wrongState.state)).toBe(false);
456
- expect(assertIsChatRoomState(wrongState.state)).toThrow();
457
- expect(isChatRoomDocument(wrongState)).toBe(false);
458
- expect(assertIsChatRoomDocument(wrongState)).toThrow();
459
- } catch (error) {
460
- expect(error).toBeInstanceOf(ZodError);
461
- }
462
-
463
- const wrongInitialState = utils.createDocument();
464
- // @ts-expect-error - we are testing the error case
465
- wrongInitialState.initialState.global = {
466
- ...{ notWhat: "you want" },
467
- };
468
- try {
469
- expect(isChatRoomState(wrongInitialState.state)).toBe(false);
470
- expect(assertIsChatRoomState(wrongInitialState.state)).toThrow();
471
- expect(isChatRoomDocument(wrongInitialState)).toBe(false);
472
- expect(assertIsChatRoomDocument(wrongInitialState)).toThrow();
473
- } catch (error) {
474
- expect(error).toBeInstanceOf(ZodError);
475
- }
476
-
477
- const missingIdInHeader = utils.createDocument();
478
- // @ts-expect-error - we are testing the error case
479
- delete missingIdInHeader.header.id;
480
- try {
481
- expect(isChatRoomDocument(missingIdInHeader)).toBe(false);
482
- expect(assertIsChatRoomDocument(missingIdInHeader)).toThrow();
483
- } catch (error) {
484
- expect(error).toBeInstanceOf(ZodError);
485
- }
486
-
487
- const missingNameInHeader = utils.createDocument();
488
- // @ts-expect-error - we are testing the error case
489
- delete missingNameInHeader.header.name;
490
- try {
491
- expect(isChatRoomDocument(missingNameInHeader)).toBe(false);
492
- expect(assertIsChatRoomDocument(missingNameInHeader)).toThrow();
493
- } catch (error) {
494
- expect(error).toBeInstanceOf(ZodError);
495
- }
496
-
497
- const missingCreatedAtUtcIsoInHeader = utils.createDocument();
498
- // @ts-expect-error - we are testing the error case
499
- delete missingCreatedAtUtcIsoInHeader.header.createdAtUtcIso;
500
- try {
501
- expect(isChatRoomDocument(missingCreatedAtUtcIsoInHeader)).toBe(false);
502
- expect(assertIsChatRoomDocument(missingCreatedAtUtcIsoInHeader)).toThrow();
503
- } catch (error) {
504
- expect(error).toBeInstanceOf(ZodError);
505
- }
506
-
507
- const missingLastModifiedAtUtcIsoInHeader = utils.createDocument();
508
- // @ts-expect-error - we are testing the error case
509
- delete missingLastModifiedAtUtcIsoInHeader.header.lastModifiedAtUtcIso;
510
- try {
511
- expect(isChatRoomDocument(missingLastModifiedAtUtcIsoInHeader)).toBe(false);
512
- expect(
513
- assertIsChatRoomDocument(missingLastModifiedAtUtcIsoInHeader),
514
- ).toThrow();
515
- } catch (error) {
516
- expect(error).toBeInstanceOf(ZodError);
517
- }
518
352
  });
519
353
  ```
520
354
 
521
- </details>
522
-
523
- ## Run the tests
524
-
525
355
  Now you can run the tests to make sure the operation reducers are working as expected.
526
356
 
527
357
  ```bash
528
- pnpm run test
358
+ npm run test
529
359
  ```
530
360
 
531
- Output should be similar to:
361
+ Output should be as follows:
532
362
 
533
363
  ```bash
534
- document-models/chat-room/src/tests/document-model.test.ts (3 tests) 1ms
535
- document-models/chat-room/src/tests/messages.test.ts (3 tests) 8ms
536
- ✓ document-models/chat-room/src/tests/settings.test.ts (2 tests) 2ms
537
-
538
- Test Files 3 passed (3)
539
- Tests 8 passed (8)
364
+ Test Files 2 passed (2)
365
+ Tests 7 passed (7)
540
366
  Start at 15:19:52
541
367
  Duration 3.61s (transform 77ms, setup 0ms, collect 3.50s, tests 14ms, environment 0ms, prepare 474ms)
542
368
  ```
543
369
 
544
- If you got a similar output, you have successfully implemented the operation reducers and tests for the **ChatRoom** document model.
545
-
546
- ## Compare with reference implementation
547
-
548
- Verify your implementation matches the tutorial:
549
-
550
- ```bash
551
- # View reference reducer implementation
552
- git show tutorial/main:document-models/chat-room/src/reducers/messages.ts
553
- git show tutorial/main:document-models/chat-room/src/reducers/settings.ts
554
-
555
- # View reference test implementation
556
- git show tutorial/main:document-models/chat-room/src/tests/messages.test.ts
557
- git show tutorial/main:document-models/chat-room/src/tests/settings.test.ts
558
-
559
- # Compare your entire implementation
560
- git diff tutorial/main -- document-models/chat-room/src/
561
- ```
562
-
563
- ## Up next: ChatRoom editor
564
-
565
- 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.
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.