@powerhousedao/academy 5.1.0-dev.1 → 5.1.0-dev.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/blog/BeyondCommunication-ABlueprintForDevelopment.md +2 -1
- package/blog/TheChallengeOfChange.md +1 -0
- package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +24 -27
- package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +118 -9
- package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +110 -28
- package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +191 -145
- package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +378 -0
- package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +560 -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/{01-GetStarted → 02-MasteryTrack/01-BuilderEnvironment}/05-VetraStudio.md +48 -6
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +75 -44
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +28 -22
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +28 -31
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +211 -206
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +176 -62
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +21 -0
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +309 -319
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +4 -0
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +4 -0
- package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
- package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +111 -35
- package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +255 -76
- package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +281 -160
- package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +188 -35
- package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +95 -7
- package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
- package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +24 -0
- package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +211 -0
- package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +171 -0
- package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +462 -0
- package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +45 -0
- package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +422 -0
- package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +61 -0
- package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +384 -0
- package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +8 -0
- package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +7 -0
- package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +9 -0
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +6 -2
- package/docs/academy/04-APIReferences/01-ReactHooks.md +2 -2
- package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +160 -0
- package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +316 -0
- package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +672 -0
- package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +957 -0
- package/docs/academy/04-APIReferences/renown-sdk/_category_.json +8 -0
- package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +7 -39
- package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +72 -24
- package/docs/academy/08-Glossary.md +7 -0
- package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +171 -0
- package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +462 -0
- package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +422 -0
- package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +370 -0
- package/docusaurus.config.ts +28 -3
- package/package.json +1 -1
- package/sidebars.ts +49 -12
- package/src/css/custom.css +26 -18
- package/static/img/Vetra-logo-dark.svg +11 -0
- package/static/img/vetra-logo-light.svg +11 -0
- package/docs/academy/00-EthereumArgentinaHackathon.md +0 -207
- package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +0 -218
- package/docs/academy/01-GetStarted/06-ReactorMCP.md +0 -58
- package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +0 -65
- package/docs/academy/05-Architecture/04-MovingBeyondCRUD +0 -61
- /package/docs/academy/{01-GetStarted → 02-MasteryTrack/01-BuilderEnvironment}/images/Modules.png +0 -0
- /package/docs/academy/{01-GetStarted → 02-MasteryTrack/01-BuilderEnvironment}/images/VetraStudioDrive.png +0 -0
- /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline → 02-RevisionHistoryTimeline/360/237/232/247"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{01-WhatIsADocumentModel → 360/237/232/247 /01-WhatIsADocumentModel"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{02-DAOandDocumentsModelsQ+A → 360/237/232/247 /02-DAOandDocumentsModelsQ+A"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{02-domain-modeling → 360/237/232/247 /02-domain-modeling"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{03-BenefitsOfDocumentModels → 360/237/232/247 /03-BenefitsOfDocumentModels"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{04-UtilitiesAndTips → 360/237/232/247 /04-UtilitiesAndTips"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{05-best-practices → 360/237/232/247 /05-best-practices"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{_category_.json → 360/237/232/247 /_category_.json"} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{three-data-layers.png → 360/237/232/247 /three-data-layers.png"} +0 -0
|
@@ -1,159 +1,268 @@
|
|
|
1
|
-
# Implement
|
|
1
|
+
# Implement the document model
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
:::tip Tutorial Repository
|
|
4
|
+
📦 **Reference Code**: [chatroom-demo](https://github.com/powerhouse-inc/chatroom-demo)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
This tutorial covers two key implementations:
|
|
7
|
+
1. **Reducers**: Implementing the reducer logic for all ChatRoom operations
|
|
8
|
+
2. **Tests**: Writing comprehensive tests for the reducers
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
You can view the exact implementation in the repository's `document-models/chat-room/src/` directory.
|
|
11
|
+
:::
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
<details>
|
|
14
|
+
<summary>📖 How to use this tutorial</summary>
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
- Or drag&drop the file into the powerhouse project directory in the VSCode editor as seen in the image below:
|
|
16
|
+
This tutorial covers implementing reducers and tests.
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
### Compare your reducer implementation
|
|
19
|
+
|
|
20
|
+
After implementing your reducers:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Compare your reducers with the reference
|
|
24
|
+
git diff tutorial/main -- document-models/chat-room/src/reducers/
|
|
25
|
+
|
|
26
|
+
# View the reference reducer implementation
|
|
27
|
+
git show tutorial/main:document-models/chat-room/src/reducers/messages.ts
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Compare your tests
|
|
31
|
+
|
|
32
|
+
After writing tests:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Compare your tests with the reference
|
|
36
|
+
git diff tutorial/main -- document-models/chat-room/src/tests/
|
|
37
|
+
|
|
38
|
+
# View the reference test implementation
|
|
39
|
+
git show tutorial/main:document-models/chat-room/src/tests/messages.test.ts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Visual comparison with GitHub Desktop
|
|
43
|
+
|
|
44
|
+
After committing your work, compare visually:
|
|
45
|
+
1. **Branch** menu → **"Compare to Branch..."**
|
|
46
|
+
2. Select `tutorial/main`
|
|
47
|
+
3. Review differences in the visual interface
|
|
48
|
+
|
|
49
|
+
See step 1 for detailed GitHub Desktop instructions.
|
|
50
|
+
|
|
51
|
+
### If you get stuck
|
|
52
|
+
|
|
53
|
+
View or reset to the reference:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# View the reducer code from the reference
|
|
57
|
+
git show tutorial/main:document-models/chat-room/src/reducers/messages.ts
|
|
58
|
+
|
|
59
|
+
# Reset to reference (WARNING: loses your changes)
|
|
60
|
+
git reset --hard tutorial/main
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
</details>
|
|
64
|
+
|
|
65
|
+
In this section, we will implement and test the operation reducers for the **ChatRoom** document model. For this, you have to export the document model specification from the Connect application and import it into your Powerhouse project directory.
|
|
66
|
+
|
|
67
|
+
To export the document model specification, follow the steps in the [Define ChatRoom Document Model](/academy/ExampleUsecases/Chatroom/DefineChatroomDocumentModel) section.
|
|
68
|
+
|
|
69
|
+
## Understanding reducers in document models
|
|
70
|
+
|
|
71
|
+
Reducers are a core concept in Powerhouse document models. They implement the state transition logic for each operation defined in your schema.
|
|
72
|
+
|
|
73
|
+
:::info
|
|
74
|
+
**Connection to schema definition language (SDL)**: The reducers directly implement the operations you defined in your SDL. Remember how we defined `AddMessageInput`, `AddEmojiReactionInput`, `RemoveEmojiReactionInput`, `EditChatNameInput`, and `EditChatDescriptionInput` in our schema?
|
|
75
|
+
The reducers provide the actual implementation of what happens when those operations are performed.
|
|
76
|
+
:::
|
|
77
|
+
|
|
78
|
+
To import the document model specification into your Powerhouse project, you can either:
|
|
79
|
+
|
|
80
|
+
- Copy and paste the file directly into the root of your Powerhouse project.
|
|
81
|
+
- Or drag and drop the file into the Powerhouse project directory in the VSCode editor as seen in the image below:
|
|
82
|
+
|
|
83
|
+
Either step will import the document model specification into your Powerhouse project.
|
|
15
84
|
|
|
16
85
|

|
|
17
86
|
|
|
18
|
-
|
|
87
|
+
## In your project directory
|
|
88
|
+
|
|
89
|
+
The next steps will take place in the VSCode editor. Make sure to have it open and the terminal window inside VSCode open as well.
|
|
19
90
|
|
|
20
|
-
To write the
|
|
91
|
+
To write the operation reducers of the **ChatRoom** document model, you need to generate the document model code from the document model specification file you have exported into the Powerhouse project directory.
|
|
21
92
|
|
|
22
93
|
To do this, run the following command in the terminal:
|
|
23
94
|
|
|
24
95
|
```bash
|
|
25
|
-
ph generate ChatRoom.
|
|
96
|
+
ph generate ChatRoom.phd
|
|
26
97
|
```
|
|
27
98
|
|
|
28
|
-
You will see that this action created a range of files for you. Before diving in
|
|
99
|
+
You will see that this action created a range of files for you. Before diving in, let's look at this simple schema to familiarize yourself with the structure you've defined in the document model once more. It shows how each type is connected to the next one.
|
|
29
100
|
|
|
30
101
|

|
|
31
102
|
|
|
32
|
-
Now you can navigate to `/document-models/chat-room/src/reducers/
|
|
103
|
+
Now you can navigate to `/document-models/chat-room/src/reducers/messages.ts` and start writing the operation reducers.
|
|
33
104
|
|
|
34
|
-
Open the `
|
|
105
|
+
Open the `messages.ts` file and you should see the scaffolding code that needs to be filled for the five operations you have specified earlier. The generated file will look like this:
|
|
35
106
|
|
|
36
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
import type { ChatRoomMessagesOperations } from "chatroom/document-models/chat-room";
|
|
37
109
|
|
|
38
|
-
|
|
110
|
+
export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
|
|
111
|
+
addMessageOperation(state, action) {
|
|
112
|
+
// TODO: Implement "addMessageOperation" reducer
|
|
113
|
+
throw new Error('Reducer "addMessageOperation" not yet implemented');
|
|
114
|
+
},
|
|
115
|
+
senderOperation(state, action) {
|
|
116
|
+
// TODO: Implement "senderOperation" reducer
|
|
117
|
+
throw new Error('Reducer "senderOperation" not yet implemented');
|
|
118
|
+
},
|
|
119
|
+
addEmojiReactionOperation(state, action) {
|
|
120
|
+
// TODO: Implement "addEmojiReactionOperation" reducer
|
|
121
|
+
throw new Error('Reducer "addEmojiReactionOperation" not yet implemented');
|
|
122
|
+
},
|
|
123
|
+
removeEmojiReactionOperation(state, action) {
|
|
124
|
+
// TODO: Implement "removeEmojiReactionOperation" reducer
|
|
125
|
+
throw new Error('Reducer "removeEmojiReactionOperation" not yet implemented');
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
```
|
|
39
129
|
|
|
40
|
-
|
|
41
|
-
2. Save the `general-operations.ts` file.
|
|
130
|
+
## Write the operation reducers
|
|
42
131
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* This is a scaffold file meant for customization:
|
|
46
|
-
* - modify it by implementing the reducer functions
|
|
47
|
-
* - delete the file and run the code generator again to have it reset
|
|
48
|
-
*/
|
|
132
|
+
1. Copy and paste the code below into the `messages.ts` file in the `reducers` folder.
|
|
133
|
+
2. Save the file.
|
|
49
134
|
|
|
50
|
-
|
|
51
|
-
|
|
135
|
+
<details>
|
|
136
|
+
<summary>Operation Reducers</summary>
|
|
52
137
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
138
|
+
```typescript
|
|
139
|
+
import {
|
|
140
|
+
MessageNotFoundError,
|
|
141
|
+
MessageContentCannotBeEmptyError,
|
|
142
|
+
} from "../../gen/messages/error.js";
|
|
143
|
+
import type { ChatRoomMessagesOperations } from "chatroom-package/document-models/chat-room";
|
|
144
|
+
|
|
145
|
+
export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
|
|
146
|
+
addMessageOperation(state, action) {
|
|
147
|
+
if (action.input.content === "") {
|
|
148
|
+
throw new MessageContentCannotBeEmptyError();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const newMessage = {
|
|
152
|
+
id: action.input.messageId,
|
|
153
|
+
sender: {
|
|
154
|
+
id: action.input.sender.id,
|
|
155
|
+
name: action.input.sender.name || null,
|
|
156
|
+
avatarUrl: action.input.sender.avatarUrl || null,
|
|
157
|
+
},
|
|
158
|
+
content: action.input.content,
|
|
159
|
+
sentAt: action.input.sentAt,
|
|
160
|
+
reactions: [],
|
|
161
|
+
};
|
|
59
162
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
content: action.input.content,
|
|
68
|
-
sentAt: action.input.sentAt,
|
|
69
|
-
reactions: [],
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
state.messages.push(newMessage);
|
|
73
|
-
},
|
|
74
|
-
addEmojiReactionOperation(state, action) {
|
|
75
|
-
const message = state.messages.find(
|
|
76
|
-
(m) => m.id === action.input.messageId,
|
|
163
|
+
state.messages.push(newMessage);
|
|
164
|
+
},
|
|
165
|
+
addEmojiReactionOperation(state, action) {
|
|
166
|
+
const message = state.messages.find((m) => m.id === action.input.messageId);
|
|
167
|
+
if (!message) {
|
|
168
|
+
throw new MessageNotFoundError(
|
|
169
|
+
`Message with ID ${action.input.messageId} not found`,
|
|
77
170
|
);
|
|
78
|
-
|
|
79
|
-
return state;
|
|
80
|
-
}
|
|
171
|
+
}
|
|
81
172
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
173
|
+
if (!message.reactions) {
|
|
174
|
+
message.reactions = [];
|
|
175
|
+
}
|
|
85
176
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
177
|
+
const existingReaction = message.reactions.find(
|
|
178
|
+
(r) => r.type === action.input.type,
|
|
179
|
+
);
|
|
89
180
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
message.reactions.push({
|
|
96
|
-
type: action.input.type,
|
|
97
|
-
reactedBy: [action.input.reactedBy],
|
|
98
|
-
});
|
|
181
|
+
if (existingReaction) {
|
|
182
|
+
if (!existingReaction.reactedBy.includes(action.input.reactedBy)) {
|
|
183
|
+
existingReaction.reactedBy.push(action.input.reactedBy);
|
|
99
184
|
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
185
|
+
} else {
|
|
186
|
+
message.reactions.push({
|
|
187
|
+
type: action.input.type,
|
|
188
|
+
reactedBy: [action.input.reactedBy],
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
removeEmojiReactionOperation(state, action) {
|
|
193
|
+
const message = state.messages.find((m) => m.id === action.input.messageId);
|
|
194
|
+
if (!message) {
|
|
195
|
+
throw new MessageNotFoundError(
|
|
196
|
+
`Message with ID ${action.input.messageId} not found`,
|
|
104
197
|
);
|
|
105
|
-
|
|
106
|
-
return state;
|
|
107
|
-
}
|
|
198
|
+
}
|
|
108
199
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
200
|
+
if (!message.reactions) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
112
203
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
204
|
+
const reactionIndex = message.reactions.findIndex(
|
|
205
|
+
(r) => r.type === action.input.type,
|
|
206
|
+
);
|
|
207
|
+
if (reactionIndex === -1) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
119
210
|
|
|
120
|
-
|
|
121
|
-
|
|
211
|
+
const reaction = message.reactions[reactionIndex];
|
|
212
|
+
const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
|
|
122
213
|
|
|
123
|
-
|
|
124
|
-
|
|
214
|
+
if (userIndex !== -1) {
|
|
215
|
+
reaction.reactedBy.splice(userIndex, 1);
|
|
125
216
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
217
|
+
if (reaction.reactedBy.length === 0) {
|
|
218
|
+
message.reactions.splice(reactionIndex, 1);
|
|
129
219
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
```
|
|
224
|
+
</details>
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
1. Do the same for the reducers of our "settings" operations. Copy and paste the code below into the `settings.ts` file in the `reducers` folder.
|
|
228
|
+
2. Save the file.
|
|
138
229
|
|
|
230
|
+
|
|
231
|
+
<details>
|
|
232
|
+
<summary>Operation Reducers</summary>
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import type { ChatRoomSettingsOperations } from "chatroom-package/document-models/chat-room";
|
|
236
|
+
|
|
237
|
+
export const chatRoomSettingsOperations: ChatRoomSettingsOperations = {
|
|
238
|
+
editChatNameOperation(state, action) {
|
|
239
|
+
state.name = action.input.name || "";
|
|
240
|
+
},
|
|
241
|
+
editChatDescriptionOperation(state, action) {
|
|
242
|
+
state.description = action.input.description || "";
|
|
243
|
+
},
|
|
244
|
+
};
|
|
139
245
|
```
|
|
140
246
|
|
|
141
|
-
|
|
247
|
+
</details>
|
|
142
248
|
|
|
143
|
-
In order to make sure the operation reducers are working as expected before implementing an editor interface, you need to write tests for them.
|
|
144
249
|
|
|
145
|
-
|
|
250
|
+
## Write the operation reducer tests
|
|
146
251
|
|
|
147
|
-
|
|
252
|
+
In order to make sure the operation reducers are working as expected, you need to write tests for them.
|
|
148
253
|
|
|
149
|
-
|
|
254
|
+
Navigate to `/document-models/chat-room/src/tests` and replace the scaffolding code with comprehensive tests.
|
|
150
255
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
256
|
+
Here are the tests for the five operations implemented in the reducers file. The test file:
|
|
257
|
+
- Uses Vitest for testing
|
|
258
|
+
- Creates an empty ChatRoom document model
|
|
259
|
+
- Tests add message, add/remove reactions, and edit operations
|
|
260
|
+
- Verifies both the operation history and the resulting state
|
|
261
|
+
|
|
262
|
+
<details>
|
|
263
|
+
<summary>Operation Reducer Tests</summary>
|
|
156
264
|
|
|
265
|
+
```typescript
|
|
157
266
|
import { describe, it, expect, beforeEach } from "vitest";
|
|
158
267
|
import { generateId } from "document-model/core";
|
|
159
268
|
import {
|
|
@@ -181,8 +290,8 @@ describe("GeneralOperations Operations", () => {
|
|
|
181
290
|
document = utils.createDocument();
|
|
182
291
|
});
|
|
183
292
|
|
|
293
|
+
// Helper function to add a message for testing
|
|
184
294
|
const addMessageHelper = (): [ChatRoomDocument, AddMessageInput] => {
|
|
185
|
-
// This is a helper function for our upcoming tests
|
|
186
295
|
const input: AddMessageInput = {
|
|
187
296
|
content: "Hello, World!",
|
|
188
297
|
messageId: generateId(),
|
|
@@ -199,30 +308,29 @@ describe("GeneralOperations Operations", () => {
|
|
|
199
308
|
return [updatedDocument, input];
|
|
200
309
|
};
|
|
201
310
|
|
|
311
|
+
// Test adding a new message
|
|
202
312
|
it("should handle addMessage operation", () => {
|
|
203
313
|
const [updatedDocument, input] = addMessageHelper();
|
|
204
314
|
|
|
205
|
-
|
|
206
|
-
expect(updatedDocument.operations.global
|
|
207
|
-
|
|
208
|
-
);
|
|
209
|
-
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
|
|
210
|
-
input,
|
|
211
|
-
);
|
|
315
|
+
// Verify the operation was recorded
|
|
316
|
+
expect(updatedDocument.operations.global).toHaveLength(1);
|
|
317
|
+
expect(updatedDocument.operations.global[0].action.type).toBe("ADD_MESSAGE");
|
|
318
|
+
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
|
|
212
319
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
213
320
|
|
|
214
|
-
|
|
321
|
+
// Verify the message was added to state
|
|
322
|
+
expect(updatedDocument.state.global.messages).toHaveLength(1);
|
|
215
323
|
expect(updatedDocument.state.global.messages[0]).toMatchObject({
|
|
216
324
|
id: input.messageId,
|
|
217
325
|
content: input.content,
|
|
218
326
|
sender: input.sender,
|
|
219
327
|
sentAt: input.sentAt,
|
|
220
|
-
reactions: [],
|
|
328
|
+
reactions: [],
|
|
221
329
|
});
|
|
222
330
|
});
|
|
223
331
|
|
|
332
|
+
// Test adding an emoji reaction
|
|
224
333
|
it("should handle addEmojiReaction operation", () => {
|
|
225
|
-
// We're validating that we can react using an emoji with a helper function
|
|
226
334
|
const [doc, addMessageInput] = addMessageHelper();
|
|
227
335
|
|
|
228
336
|
let updatedDocument = doc;
|
|
@@ -238,26 +346,24 @@ describe("GeneralOperations Operations", () => {
|
|
|
238
346
|
addEmojiReaction(addEmojiReactionInput),
|
|
239
347
|
);
|
|
240
348
|
|
|
241
|
-
|
|
242
|
-
expect(updatedDocument.operations.global
|
|
243
|
-
|
|
244
|
-
);
|
|
349
|
+
// Verify the operation was recorded
|
|
350
|
+
expect(updatedDocument.operations.global).toHaveLength(2);
|
|
351
|
+
expect(updatedDocument.operations.global[1].action.type).toBe("ADD_EMOJI_REACTION");
|
|
245
352
|
expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
|
|
246
353
|
addEmojiReactionInput,
|
|
247
354
|
);
|
|
248
355
|
expect(updatedDocument.operations.global[1].index).toEqual(1);
|
|
249
356
|
|
|
250
|
-
|
|
251
|
-
expect(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
reactedBy: [addEmojiReactionInput.reactedBy], // We're validating that reactedBy object only contains the right address
|
|
357
|
+
// Verify the reaction was added
|
|
358
|
+
expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(1);
|
|
359
|
+
expect(updatedDocument.state.global.messages[0].reactions?.[0]).toMatchObject({
|
|
360
|
+
reactedBy: [addEmojiReactionInput.reactedBy],
|
|
255
361
|
type: addEmojiReactionInput.type,
|
|
256
362
|
});
|
|
257
363
|
});
|
|
258
364
|
|
|
365
|
+
// Test adding reaction to non-existing message
|
|
259
366
|
it("should handle addEmojiReaction operation to a non existing message", () => {
|
|
260
|
-
// We're testing that the state doesn't change when reacting to a non-existing message
|
|
261
367
|
const input: AddEmojiReactionInput = {
|
|
262
368
|
messageId: "invalid-message-id",
|
|
263
369
|
reactedBy: "anon-user",
|
|
@@ -267,14 +373,12 @@ describe("GeneralOperations Operations", () => {
|
|
|
267
373
|
const updatedDocument = reducer(document, addEmojiReaction(input));
|
|
268
374
|
|
|
269
375
|
expect(updatedDocument.operations.global).toHaveLength(1);
|
|
270
|
-
expect(updatedDocument.operations.global[0].action.type).toBe(
|
|
271
|
-
"ADD_EMOJI_REACTION",
|
|
272
|
-
);
|
|
376
|
+
expect(updatedDocument.operations.global[0].action.type).toBe("ADD_EMOJI_REACTION");
|
|
273
377
|
expect(updatedDocument.state.global.messages).toHaveLength(0);
|
|
274
378
|
});
|
|
275
379
|
|
|
380
|
+
// Test removing an emoji reaction
|
|
276
381
|
it("should handle removeEmojiReaction operation", () => {
|
|
277
|
-
// We're making use of a helper function to check if we can remove an EmojiReaction
|
|
278
382
|
const [doc, addMessageInput] = addMessageHelper();
|
|
279
383
|
|
|
280
384
|
let updatedDocument = doc;
|
|
@@ -291,7 +395,6 @@ describe("GeneralOperations Operations", () => {
|
|
|
291
395
|
);
|
|
292
396
|
|
|
293
397
|
const input: RemoveEmojiReactionInput = {
|
|
294
|
-
// We're validating the removal of a message by our anon-user with a specific messageId
|
|
295
398
|
messageId: addMessageInput.messageId,
|
|
296
399
|
senderId: "anon-user",
|
|
297
400
|
type: "THUMBS_UP",
|
|
@@ -299,19 +402,17 @@ describe("GeneralOperations Operations", () => {
|
|
|
299
402
|
|
|
300
403
|
updatedDocument = reducer(updatedDocument, removeEmojiReaction(input));
|
|
301
404
|
|
|
302
|
-
|
|
303
|
-
expect(updatedDocument.operations.global
|
|
304
|
-
|
|
305
|
-
);
|
|
306
|
-
expect(updatedDocument.operations.global[2].action.input).toStrictEqual(
|
|
307
|
-
input,
|
|
308
|
-
);
|
|
405
|
+
// Verify the operation was recorded
|
|
406
|
+
expect(updatedDocument.operations.global).toHaveLength(3);
|
|
407
|
+
expect(updatedDocument.operations.global[2].action.type).toBe("REMOVE_EMOJI_REACTION");
|
|
408
|
+
expect(updatedDocument.operations.global[2].action.input).toStrictEqual(input);
|
|
309
409
|
expect(updatedDocument.operations.global[2].index).toEqual(2);
|
|
310
410
|
|
|
311
|
-
//
|
|
411
|
+
// Verify the reaction was removed
|
|
312
412
|
expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(0);
|
|
313
413
|
});
|
|
314
414
|
|
|
415
|
+
// Test editing chat name
|
|
315
416
|
it("should handle editChatName operation", () => {
|
|
316
417
|
const input: EditChatNameInput = {
|
|
317
418
|
name: "New Chat Name",
|
|
@@ -319,18 +420,17 @@ describe("GeneralOperations Operations", () => {
|
|
|
319
420
|
|
|
320
421
|
const updatedDocument = reducer(document, editChatName(input));
|
|
321
422
|
|
|
322
|
-
|
|
323
|
-
expect(updatedDocument.operations.global
|
|
324
|
-
|
|
325
|
-
);
|
|
326
|
-
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
|
|
327
|
-
input,
|
|
328
|
-
);
|
|
423
|
+
// Verify the operation was recorded
|
|
424
|
+
expect(updatedDocument.operations.global).toHaveLength(1);
|
|
425
|
+
expect(updatedDocument.operations.global[0].action.type).toBe("EDIT_CHAT_NAME");
|
|
426
|
+
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
|
|
329
427
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
330
428
|
|
|
429
|
+
// Verify the name was updated
|
|
331
430
|
expect(updatedDocument.state.global.name).toBe(input.name);
|
|
332
431
|
});
|
|
333
432
|
|
|
433
|
+
// Test editing chat description
|
|
334
434
|
it("should handle editChatDescription operation", () => {
|
|
335
435
|
const input: EditChatDescriptionInput = {
|
|
336
436
|
description: "New Chat Description",
|
|
@@ -338,34 +438,55 @@ describe("GeneralOperations Operations", () => {
|
|
|
338
438
|
|
|
339
439
|
const updatedDocument = reducer(document, editChatDescription(input));
|
|
340
440
|
|
|
341
|
-
|
|
342
|
-
expect(updatedDocument.operations.global
|
|
343
|
-
|
|
344
|
-
);
|
|
345
|
-
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
|
|
346
|
-
input,
|
|
347
|
-
);
|
|
441
|
+
// Verify the operation was recorded
|
|
442
|
+
expect(updatedDocument.operations.global).toHaveLength(1);
|
|
443
|
+
expect(updatedDocument.operations.global[0].action.type).toBe("EDIT_CHAT_DESCRIPTION");
|
|
444
|
+
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
|
|
348
445
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
349
446
|
|
|
447
|
+
// Verify the description was updated
|
|
350
448
|
expect(updatedDocument.state.global.description).toBe(input.description);
|
|
351
449
|
});
|
|
352
450
|
});
|
|
353
451
|
```
|
|
354
452
|
|
|
453
|
+
</details>
|
|
454
|
+
|
|
355
455
|
Now you can run the tests to make sure the operation reducers are working as expected.
|
|
356
456
|
|
|
357
457
|
```bash
|
|
358
|
-
|
|
458
|
+
pnpm run test
|
|
359
459
|
```
|
|
360
460
|
|
|
361
461
|
Output should be as follows:
|
|
362
462
|
|
|
363
463
|
```bash
|
|
464
|
+
✓ document-models/chat-room/src/tests/document-model.test.ts (3 tests) 1ms
|
|
465
|
+
✓ document-models/chat-room/src/tests/general-operations.test.ts (6 tests) 8ms
|
|
466
|
+
|
|
364
467
|
Test Files 2 passed (2)
|
|
365
|
-
Tests
|
|
468
|
+
Tests 9 passed (9)
|
|
366
469
|
Start at 15:19:52
|
|
367
470
|
Duration 3.61s (transform 77ms, setup 0ms, collect 3.50s, tests 14ms, environment 0ms, prepare 474ms)
|
|
368
471
|
```
|
|
369
472
|
|
|
370
|
-
If you got the same output, you have successfully implemented the operation reducers and tests for the
|
|
371
|
-
|
|
473
|
+
If you got the same output, you have successfully implemented the operation reducers and tests for the **ChatRoom** document model.
|
|
474
|
+
|
|
475
|
+
## Compare with reference implementation
|
|
476
|
+
|
|
477
|
+
Verify your implementation matches the tutorial:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
# View reference reducer implementation
|
|
481
|
+
git show tutorial/main:document-models/chat-room/src/reducers/general-operations.ts
|
|
482
|
+
|
|
483
|
+
# View reference test implementation
|
|
484
|
+
git show tutorial/main:document-models/chat-room/src/tests/general-operations.test.ts
|
|
485
|
+
|
|
486
|
+
# Compare your entire implementation
|
|
487
|
+
git diff tutorial/main -- document-models/chat-room/src/
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Up next: ChatRoom editor
|
|
491
|
+
|
|
492
|
+
Continue to the next section to learn how to implement the document model editor so you can see a simple user interface for the **ChatRoom** document model in action.
|