@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.
- package/CHANGELOG.md +46 -1148
- 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 +10 -155
- package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +35 -122
- package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +155 -178
- package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
- package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +22 -62
- 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 +79 -195
- package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +241 -435
- package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +27 -388
- 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 +13 -49
- package/src/css/custom.css +18 -26
- package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -425
- package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -557
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/Modules.png +0 -0
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/VetraStudioDrive.png +0 -0
- package/docs/academy/02-MasteryTrack/05-Launch/05-DockerDeployment.md +0 -384
- 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/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,262 +1,152 @@
|
|
|
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
|
-
|
|
32
|
+
Now you can navigate to `/document-models/chat-room/src/reducers/general-operations.ts` and start writing the operation reducers.
|
|
104
33
|
|
|
105
|
-
|
|
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
|
-
|
|
36
|
+

|
|
108
37
|
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
50
|
+
import { MessageContentCannotBeEmpty } from "../../gen/general-operations/error.js";
|
|
51
|
+
import type { ChatRoomGeneralOperationsOperations } from "chatroom/document-models/chat-room";
|
|
134
52
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
82
|
+
if (!message.reactions) {
|
|
83
|
+
message.reactions = [];
|
|
84
|
+
}
|
|
173
85
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
86
|
+
const existingReaction = message.reactions.find(
|
|
87
|
+
(r) => r.type === action.input.type,
|
|
88
|
+
);
|
|
177
89
|
|
|
178
|
-
|
|
179
|
-
|
|
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
99
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
109
|
+
if (!message.reactions) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
223
112
|
|
|
224
|
-
|
|
113
|
+
const reactionIndex = message.reactions.findIndex(
|
|
114
|
+
(r) => r.type === action.input.type,
|
|
115
|
+
);
|
|
116
|
+
if (reactionIndex === -1) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
225
119
|
|
|
226
|
-
|
|
120
|
+
const reaction = message.reactions[reactionIndex];
|
|
121
|
+
const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
|
|
227
122
|
|
|
228
|
-
|
|
123
|
+
if (userIndex !== -1) {
|
|
124
|
+
reaction.reactedBy.splice(userIndex, 1);
|
|
229
125
|
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
|
|
300
|
-
const
|
|
225
|
+
// We're validating that we can react using an emoji with a helper function
|
|
226
|
+
const [doc, addMessageInput] = addMessageHelper();
|
|
301
227
|
|
|
302
|
-
|
|
228
|
+
let updatedDocument = doc;
|
|
303
229
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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[
|
|
310
|
-
|
|
245
|
+
expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
|
|
246
|
+
addEmojiReactionInput,
|
|
311
247
|
);
|
|
312
|
-
expect(updatedDocument.operations.global[
|
|
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
|
-
|
|
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
|
-
"
|
|
324
|
-
);
|
|
325
|
-
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
|
|
326
|
-
input,
|
|
271
|
+
"ADD_EMOJI_REACTION",
|
|
327
272
|
);
|
|
328
|
-
expect(updatedDocument.
|
|
273
|
+
expect(updatedDocument.state.global.messages).toHaveLength(0);
|
|
329
274
|
});
|
|
330
|
-
});
|
|
331
|
-
```
|
|
332
275
|
|
|
333
|
-
|
|
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
|
-
|
|
280
|
+
let updatedDocument = doc;
|
|
336
281
|
|
|
337
|
-
|
|
282
|
+
const addEmojiReactionInput: AddEmojiReactionInput = {
|
|
283
|
+
messageId: addMessageInput.messageId,
|
|
284
|
+
reactedBy: "anon-user",
|
|
285
|
+
type: "THUMBS_UP",
|
|
286
|
+
};
|
|
338
287
|
|
|
339
|
-
|
|
340
|
-
|
|
288
|
+
updatedDocument = reducer(
|
|
289
|
+
updatedDocument,
|
|
290
|
+
addEmojiReaction(addEmojiReactionInput),
|
|
291
|
+
);
|
|
341
292
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
363
|
-
|
|
316
|
+
const input: EditChatNameInput = {
|
|
317
|
+
name: "New Chat Name",
|
|
318
|
+
};
|
|
364
319
|
|
|
365
320
|
const updatedDocument = reducer(document, editChatName(input));
|
|
366
321
|
|
|
367
|
-
expect(
|
|
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
|
|
379
|
-
|
|
335
|
+
const input: EditChatDescriptionInput = {
|
|
336
|
+
description: "New Chat Description",
|
|
337
|
+
};
|
|
380
338
|
|
|
381
339
|
const updatedDocument = reducer(document, editChatDescription(input));
|
|
382
340
|
|
|
383
|
-
expect(
|
|
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
|
-
|
|
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
|
-
|
|
358
|
+
npm run test
|
|
529
359
|
```
|
|
530
360
|
|
|
531
|
-
Output should be
|
|
361
|
+
Output should be as follows:
|
|
532
362
|
|
|
533
363
|
```bash
|
|
534
|
-
|
|
535
|
-
|
|
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
|
|
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.
|