@powerhousedao/academy 5.1.0-dev.9 → 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.
- package/CHANGELOG.md +43 -1108
- package/blog/BeyondCommunication-ABlueprintForDevelopment.md +1 -2
- package/blog/TheChallengeOfChange.md +0 -1
- package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
- package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +27 -24
- package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +9 -118
- package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +28 -110
- package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +145 -191
- package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
- package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +6 -48
- package/docs/academy/01-GetStarted/06-ReactorMCP.md +58 -0
- package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +1 -1
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +2 -2
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +44 -75
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +22 -28
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +31 -28
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +206 -211
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +62 -176
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +0 -21
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +319 -309
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +0 -4
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +0 -4
- package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
- package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +35 -111
- package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +76 -255
- package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +160 -281
- package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +35 -188
- package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +7 -95
- package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +2 -6
- package/docs/academy/04-APIReferences/01-ReactHooks.md +501 -291
- package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +39 -7
- package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +65 -0
- package/docs/academy/05-Architecture/04-MovingBeyondCRUD +61 -0
- package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +24 -72
- package/docs/academy/08-Glossary.md +0 -7
- package/docusaurus.config.ts +3 -28
- package/package.json +1 -1
- package/sidebars.ts +12 -49
- package/src/css/custom.css +18 -26
- package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -378
- package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -560
- package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +0 -24
- package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +0 -211
- package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
- package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +0 -462
- package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +0 -45
- package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
- package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +0 -61
- package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +0 -384
- package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +0 -8
- package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +0 -7
- package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +0 -9
- package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +0 -160
- package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +0 -316
- package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +0 -672
- package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +0 -957
- package/docs/academy/04-APIReferences/renown-sdk/_category_.json +0 -8
- package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
- package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +0 -462
- package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
- package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +0 -370
- package/static/img/Vetra-logo-dark.svg +0 -11
- package/static/img/vetra-logo-light.svg +0 -11
- /package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/images/Modules.png +0 -0
- /package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/images/VetraStudioDrive.png +0 -0
- /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline/360/237/232/247" → 02-RevisionHistoryTimeline} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /01-WhatIsADocumentModel" → 01-WhatIsADocumentModel} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-DAOandDocumentsModelsQ+A" → 02-DAOandDocumentsModelsQ+A} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-domain-modeling" → 02-domain-modeling} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /03-BenefitsOfDocumentModels" → 03-BenefitsOfDocumentModels} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /04-UtilitiesAndTips" → 04-UtilitiesAndTips} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /05-best-practices" → 05-best-practices} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /_category_.json" → _category_.json} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /three-data-layers.png" → three-data-layers.png} +0 -0
|
@@ -1,268 +1,159 @@
|
|
|
1
|
-
# Implement
|
|
1
|
+
# Implement Operation Reducers
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
:::
|
|
7
|
+
## Import Document Model and Generate Code
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
<summary>📖 How to use this tutorial</summary>
|
|
9
|
+
To import the document model into your powerhouse project, you can either:
|
|
15
10
|
|
|
16
|
-
|
|
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
|
-
|
|
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
|

|
|
86
17
|
|
|
87
|
-
|
|
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
|
|
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
|
|
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
|

|
|
102
31
|
|
|
103
|
-
Now you can navigate to `/document-models/chat-room/src/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
|
-
Open the `
|
|
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
|
-
|
|
108
|
-
import type { ChatRoomMessagesOperations } from "chatroom/document-models/chat-room";
|
|
36
|
+

|
|
109
37
|
|
|
110
|
-
|
|
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
|
-
```
|
|
38
|
+
## Write the Operation Reducers
|
|
129
39
|
|
|
130
|
-
|
|
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.
|
|
131
42
|
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
*/
|
|
134
49
|
|
|
135
|
-
|
|
136
|
-
|
|
50
|
+
import { MessageContentCannotBeEmpty } from "../../gen/general-operations/error.js";
|
|
51
|
+
import type { ChatRoomGeneralOperationsOperations } from "chatroom/document-models/chat-room";
|
|
137
52
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
};
|
|
53
|
+
export const chatRoomGeneralOperationsOperations: ChatRoomGeneralOperationsOperations =
|
|
54
|
+
{
|
|
55
|
+
addMessageOperation(state, action) {
|
|
56
|
+
if (action.input.content === "") {
|
|
57
|
+
throw new MessageContentCannotBeEmpty();
|
|
58
|
+
}
|
|
162
59
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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,
|
|
170
77
|
);
|
|
171
|
-
|
|
78
|
+
if (!message) {
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
172
81
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
82
|
+
if (!message.reactions) {
|
|
83
|
+
message.reactions = [];
|
|
84
|
+
}
|
|
176
85
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
86
|
+
const existingReaction = message.reactions.find(
|
|
87
|
+
(r) => r.type === action.input.type,
|
|
88
|
+
);
|
|
180
89
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
});
|
|
184
99
|
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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`,
|
|
100
|
+
},
|
|
101
|
+
removeEmojiReactionOperation(state, action) {
|
|
102
|
+
const message = state.messages.find(
|
|
103
|
+
(m) => m.id === action.input.messageId,
|
|
197
104
|
);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const reactionIndex = message.reactions.findIndex(
|
|
205
|
-
(r) => r.type === action.input.type,
|
|
206
|
-
);
|
|
207
|
-
if (reactionIndex === -1) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const reaction = message.reactions[reactionIndex];
|
|
212
|
-
const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
|
|
213
|
-
|
|
214
|
-
if (userIndex !== -1) {
|
|
215
|
-
reaction.reactedBy.splice(userIndex, 1);
|
|
105
|
+
if (!message) {
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
216
108
|
|
|
217
|
-
if (
|
|
218
|
-
|
|
109
|
+
if (!message.reactions) {
|
|
110
|
+
return;
|
|
219
111
|
}
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
```
|
|
224
|
-
</details>
|
|
225
112
|
|
|
113
|
+
const reactionIndex = message.reactions.findIndex(
|
|
114
|
+
(r) => r.type === action.input.type,
|
|
115
|
+
);
|
|
116
|
+
if (reactionIndex === -1) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
226
119
|
|
|
227
|
-
|
|
228
|
-
|
|
120
|
+
const reaction = message.reactions[reactionIndex];
|
|
121
|
+
const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
|
|
229
122
|
|
|
123
|
+
if (userIndex !== -1) {
|
|
124
|
+
reaction.reactedBy.splice(userIndex, 1);
|
|
230
125
|
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
};
|
|
233
138
|
|
|
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
|
-
};
|
|
245
139
|
```
|
|
246
140
|
|
|
247
|
-
|
|
141
|
+
## Write the Operation Reducers Tests
|
|
248
142
|
|
|
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.
|
|
249
144
|
|
|
250
|
-
|
|
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.
|
|
251
146
|
|
|
252
|
-
|
|
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.
|
|
253
148
|
|
|
254
|
-
|
|
255
|
-
|
|
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>
|
|
149
|
+
Here are the tests for the five operations written in the reducers file.
|
|
264
150
|
|
|
265
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
|
+
*/
|
|
156
|
+
|
|
266
157
|
import { describe, it, expect, beforeEach } from "vitest";
|
|
267
158
|
import { generateId } from "document-model/core";
|
|
268
159
|
import {
|
|
@@ -290,8 +181,8 @@ describe("GeneralOperations Operations", () => {
|
|
|
290
181
|
document = utils.createDocument();
|
|
291
182
|
});
|
|
292
183
|
|
|
293
|
-
// Helper function to add a message for testing
|
|
294
184
|
const addMessageHelper = (): [ChatRoomDocument, AddMessageInput] => {
|
|
185
|
+
// This is a helper function for our upcoming tests
|
|
295
186
|
const input: AddMessageInput = {
|
|
296
187
|
content: "Hello, World!",
|
|
297
188
|
messageId: generateId(),
|
|
@@ -308,29 +199,30 @@ describe("GeneralOperations Operations", () => {
|
|
|
308
199
|
return [updatedDocument, input];
|
|
309
200
|
};
|
|
310
201
|
|
|
311
|
-
// Test adding a new message
|
|
312
202
|
it("should handle addMessage operation", () => {
|
|
313
203
|
const [updatedDocument, input] = addMessageHelper();
|
|
314
204
|
|
|
315
|
-
//
|
|
316
|
-
expect(updatedDocument.operations.global).
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
);
|
|
319
212
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
320
213
|
|
|
321
|
-
//
|
|
322
|
-
expect(updatedDocument.state.global.messages).toHaveLength(1);
|
|
214
|
+
expect(updatedDocument.state.global.messages).toHaveLength(1); // We're validating that the message is present in the message state of the document
|
|
323
215
|
expect(updatedDocument.state.global.messages[0]).toMatchObject({
|
|
324
216
|
id: input.messageId,
|
|
325
217
|
content: input.content,
|
|
326
218
|
sender: input.sender,
|
|
327
219
|
sentAt: input.sentAt,
|
|
328
|
-
reactions: [],
|
|
220
|
+
reactions: [], // We also want to make sure that reaction object is an empty array
|
|
329
221
|
});
|
|
330
222
|
});
|
|
331
223
|
|
|
332
|
-
// Test adding an emoji reaction
|
|
333
224
|
it("should handle addEmojiReaction operation", () => {
|
|
225
|
+
// We're validating that we can react using an emoji with a helper function
|
|
334
226
|
const [doc, addMessageInput] = addMessageHelper();
|
|
335
227
|
|
|
336
228
|
let updatedDocument = doc;
|
|
@@ -346,24 +238,26 @@ describe("GeneralOperations Operations", () => {
|
|
|
346
238
|
addEmojiReaction(addEmojiReactionInput),
|
|
347
239
|
);
|
|
348
240
|
|
|
349
|
-
//
|
|
350
|
-
expect(updatedDocument.operations.global).
|
|
351
|
-
|
|
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
|
+
);
|
|
352
245
|
expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
|
|
353
246
|
addEmojiReactionInput,
|
|
354
247
|
);
|
|
355
248
|
expect(updatedDocument.operations.global[1].index).toEqual(1);
|
|
356
249
|
|
|
357
|
-
//
|
|
358
|
-
expect(
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
361
255
|
type: addEmojiReactionInput.type,
|
|
362
256
|
});
|
|
363
257
|
});
|
|
364
258
|
|
|
365
|
-
// Test adding reaction to non-existing message
|
|
366
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
|
|
367
261
|
const input: AddEmojiReactionInput = {
|
|
368
262
|
messageId: "invalid-message-id",
|
|
369
263
|
reactedBy: "anon-user",
|
|
@@ -373,12 +267,14 @@ describe("GeneralOperations Operations", () => {
|
|
|
373
267
|
const updatedDocument = reducer(document, addEmojiReaction(input));
|
|
374
268
|
|
|
375
269
|
expect(updatedDocument.operations.global).toHaveLength(1);
|
|
376
|
-
expect(updatedDocument.operations.global[0].action.type).toBe(
|
|
270
|
+
expect(updatedDocument.operations.global[0].action.type).toBe(
|
|
271
|
+
"ADD_EMOJI_REACTION",
|
|
272
|
+
);
|
|
377
273
|
expect(updatedDocument.state.global.messages).toHaveLength(0);
|
|
378
274
|
});
|
|
379
275
|
|
|
380
|
-
// Test removing an emoji reaction
|
|
381
276
|
it("should handle removeEmojiReaction operation", () => {
|
|
277
|
+
// We're making use of a helper function to check if we can remove an EmojiReaction
|
|
382
278
|
const [doc, addMessageInput] = addMessageHelper();
|
|
383
279
|
|
|
384
280
|
let updatedDocument = doc;
|
|
@@ -395,6 +291,7 @@ describe("GeneralOperations Operations", () => {
|
|
|
395
291
|
);
|
|
396
292
|
|
|
397
293
|
const input: RemoveEmojiReactionInput = {
|
|
294
|
+
// We're validating the removal of a message by our anon-user with a specific messageId
|
|
398
295
|
messageId: addMessageInput.messageId,
|
|
399
296
|
senderId: "anon-user",
|
|
400
297
|
type: "THUMBS_UP",
|
|
@@ -402,17 +299,19 @@ describe("GeneralOperations Operations", () => {
|
|
|
402
299
|
|
|
403
300
|
updatedDocument = reducer(updatedDocument, removeEmojiReaction(input));
|
|
404
301
|
|
|
405
|
-
//
|
|
406
|
-
expect(updatedDocument.operations.global).
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
);
|
|
409
309
|
expect(updatedDocument.operations.global[2].index).toEqual(2);
|
|
410
310
|
|
|
411
|
-
//
|
|
311
|
+
// When the last user removes their reaction, the entire reaction should be removed
|
|
412
312
|
expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(0);
|
|
413
313
|
});
|
|
414
314
|
|
|
415
|
-
// Test editing chat name
|
|
416
315
|
it("should handle editChatName operation", () => {
|
|
417
316
|
const input: EditChatNameInput = {
|
|
418
317
|
name: "New Chat Name",
|
|
@@ -420,17 +319,18 @@ describe("GeneralOperations Operations", () => {
|
|
|
420
319
|
|
|
421
320
|
const updatedDocument = reducer(document, editChatName(input));
|
|
422
321
|
|
|
423
|
-
//
|
|
424
|
-
expect(updatedDocument.operations.global).
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
);
|
|
427
329
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
428
330
|
|
|
429
|
-
// Verify the name was updated
|
|
430
331
|
expect(updatedDocument.state.global.name).toBe(input.name);
|
|
431
332
|
});
|
|
432
333
|
|
|
433
|
-
// Test editing chat description
|
|
434
334
|
it("should handle editChatDescription operation", () => {
|
|
435
335
|
const input: EditChatDescriptionInput = {
|
|
436
336
|
description: "New Chat Description",
|
|
@@ -438,55 +338,34 @@ describe("GeneralOperations Operations", () => {
|
|
|
438
338
|
|
|
439
339
|
const updatedDocument = reducer(document, editChatDescription(input));
|
|
440
340
|
|
|
441
|
-
//
|
|
442
|
-
expect(updatedDocument.operations.global).
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
);
|
|
445
348
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
446
349
|
|
|
447
|
-
// Verify the description was updated
|
|
448
350
|
expect(updatedDocument.state.global.description).toBe(input.description);
|
|
449
351
|
});
|
|
450
352
|
});
|
|
451
353
|
```
|
|
452
354
|
|
|
453
|
-
</details>
|
|
454
|
-
|
|
455
355
|
Now you can run the tests to make sure the operation reducers are working as expected.
|
|
456
356
|
|
|
457
357
|
```bash
|
|
458
|
-
|
|
358
|
+
npm run test
|
|
459
359
|
```
|
|
460
360
|
|
|
461
361
|
Output should be as follows:
|
|
462
362
|
|
|
463
363
|
```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
|
-
|
|
467
364
|
Test Files 2 passed (2)
|
|
468
|
-
Tests
|
|
365
|
+
Tests 7 passed (7)
|
|
469
366
|
Start at 15:19:52
|
|
470
367
|
Duration 3.61s (transform 77ms, setup 0ms, collect 3.50s, tests 14ms, environment 0ms, prepare 474ms)
|
|
471
368
|
```
|
|
472
369
|
|
|
473
|
-
If you got the same output, you have successfully implemented the operation reducers and tests for the
|
|
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.
|
|
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.
|