@powerhousedao/academy 5.0.3 → 5.0.5
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 +8 -0
- package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
- package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +1 -1
- package/docs/academy/01-GetStarted/05-VetraStudio.md +76 -27
- package/docs/academy/01-GetStarted/home.mdx +4 -4
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/01-Prerequisites.md +24 -8
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/02-StandardDocumentModelWorkflow.md +25 -7
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +17 -3
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/01-WhatIsADocumentModel.md +5 -2
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +7 -1
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +5 -5
- package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +4 -1
- package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +21 -9
- package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +132 -129
- package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +50 -44
- package/docs/academy/03-ExampleUsecases/Chatroom/image-4.png +0 -0
- package/docs/academy/03-ExampleUsecases/Chatroom/image-5.png +0 -0
- package/docs/academy/03-ExampleUsecases/Chatroom/images/ChatRoomConnectApp.gif +0 -0
- package/docs/academy/03-ExampleUsecases/Chatroom/images/ChatRoomTest.gif +0 -0
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +39 -5
- package/docs/academy/05-Architecture/01-WorkingWithTheReactor.md +0 -2
- package/package.json +1 -1
- package/sidebars.ts +8 -0
- package/src/css/custom.css +20 -0
- package/static/img/ethereum-logo.jpeg +0 -0
- package/docs/academy/09-AIResources +0 -131
- package/docs/academy/TUTORIAL_VERIFICATION_ARCHITECTURE +0 -325
- /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline.md → 02-RevisionHistoryTimeline} +0 -0
package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md
CHANGED
|
@@ -112,6 +112,12 @@ Now, run the tests from your project's root directory to verify your implementat
|
|
|
112
112
|
pnpm run test
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
Or with npm:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm test
|
|
119
|
+
```
|
|
120
|
+
|
|
115
121
|
If all tests pass, you have successfully verified the core logic of your `To-do List` document model. This ensures that the reducers you wrote behave exactly as expected.
|
|
116
122
|
|
|
117
123
|
</details>
|
|
@@ -128,7 +134,7 @@ While the tutorial provides a concrete example, keep these general best practice
|
|
|
128
134
|
- **Assert**: Check if the outcome is as expected using `expect()`.
|
|
129
135
|
- **Test Immutability**: A key assertion is to ensure the state is not mutated directly. You can check that a new array or object was created: `expect(newState.items).not.toBe(oldState.items);`.
|
|
130
136
|
- **Cover Edge Cases**: Test what happens when an operation receives invalid input (e.g., trying to update an item that doesn't exist). Your test should confirm the reducer either throws an error or returns the state unchanged, depending on your implementation.
|
|
131
|
-
- **Run Tests Frequently**: Integrate testing into your development workflow. Run tests after making changes to ensure you haven't broken anything. The `pnpm run test` command is your friend.
|
|
137
|
+
- **Run Tests Frequently**: Integrate testing into your development workflow. Run tests after making changes to ensure you haven't broken anything. The `pnpm run test` (or `npm test`) command is your friend.
|
|
132
138
|
|
|
133
139
|
## Conclusion: The payoff of diligent testing
|
|
134
140
|
|
package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
:::info
|
|
4
4
|
|
|
5
|
-
The Todo-demo
|
|
5
|
+
The Todo-demo is maintained by the Powerhouse Team and serves as a reference for testing and introducing new features. It will be continuously updated alongside the accompanying documentation.
|
|
6
6
|
|
|
7
|
-
https://github.com/powerhouse-inc/todo-demo
|
|
7
|
+
https://github.com/powerhouse-inc/todo-demo
|
|
8
8
|
:::
|
|
9
9
|
|
|
10
10
|
There are several ways to explore this package:
|
|
11
11
|
|
|
12
|
-
### Option 1: Rebuild the Todo-demo
|
|
12
|
+
### Option 1: Rebuild the Todo-demo
|
|
13
13
|
|
|
14
|
-
The Todo-demo
|
|
14
|
+
The Todo-demo and repository are your main reference points during the Mastery Track.
|
|
15
15
|
Follow the steps in the "Mastery Track – Document Model Creation" chapters to build along with the examples.
|
|
16
16
|
|
|
17
17
|
### Option 2: Clone and run the code locally
|
|
@@ -35,5 +35,5 @@ ph connect
|
|
|
35
35
|
Alternatively, you can install this package in a Powerhouse project or in your deployed host apps:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
ph install @powerhousedao/todo-demo
|
|
38
|
+
ph install @powerhousedao/todo-demo
|
|
39
39
|
```
|
|
@@ -73,7 +73,10 @@ Watching local document editors at '/home/you/Powerhouse/ChatRoom/editors'...
|
|
|
73
73
|
|
|
74
74
|
A new browser window will open and you will see the Connect application. If it doesn't open automatically, you can open it manually by navigating to `http://localhost:3000/` in your browser.
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
If you don't have a local drive created yet, create one using the Generic Drive Explorer app.
|
|
77
|
+
|
|
78
|
+
Open your Local Drive and create a new document model by clicking the `DocumentModel` button in the "New Document" section. The GIF below shows where to click.
|
|
79
|
+
|
|
77
80
|
|
|
78
81
|

|
|
79
82
|
|
|
@@ -65,11 +65,17 @@ enum ReactionType {
|
|
|
65
65
|
|
|
66
66
|
input AddMessageInput {
|
|
67
67
|
messageId: OID! # ID of the message that is being added
|
|
68
|
-
sender:
|
|
68
|
+
sender: SenderInput! # ID of the user sending the message
|
|
69
69
|
content: String! # Content of the message
|
|
70
70
|
sentAt: DateTime!
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
input SenderInput {
|
|
74
|
+
id: ID! # Unique identifier for the sender
|
|
75
|
+
name: String
|
|
76
|
+
avatarUrl: URL # Allows us to pull the ENS and/or nft of the persons profile
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
input AddEmojiReactionInput {
|
|
74
80
|
messageId: OID! # ID of the message to which the reaction is being added
|
|
75
81
|
reactedBy: ID! # ID of the user adding the reaction
|
|
@@ -106,14 +112,14 @@ The steps below show you how to do this:
|
|
|
106
112
|
|
|
107
113
|
3. In the code editor, you can see the SDL for the document model. Replace the existing SDL with the SDL defined in the [State Schema](#state-schema) section above. Only copy and paste the types, leaving the inputs for the next step. You can however already press 'Sync with schema' button to set the initial state of your document model based on your Schema Definition Language. Verify that your Global State Initial Value looks like this.
|
|
108
114
|
|
|
109
|
-
```
|
|
115
|
+
```json
|
|
110
116
|
{
|
|
111
117
|
"id": "",
|
|
112
118
|
"name": "",
|
|
113
|
-
"description":
|
|
114
|
-
"createdAt":
|
|
115
|
-
"createdBy":
|
|
116
|
-
"messages":
|
|
119
|
+
"description": null,
|
|
120
|
+
"createdAt": null,
|
|
121
|
+
"createdBy": null,
|
|
122
|
+
"messages": []
|
|
117
123
|
}
|
|
118
124
|
```
|
|
119
125
|
|
|
@@ -123,11 +129,17 @@ The steps below show you how to do this:
|
|
|
123
129
|
|
|
124
130
|
```graphql
|
|
125
131
|
input AddMessageInput {
|
|
126
|
-
messageId: OID!
|
|
127
|
-
sender:
|
|
128
|
-
content: String!
|
|
132
|
+
messageId: OID!
|
|
133
|
+
sender: SenderInput!
|
|
134
|
+
content: String!
|
|
129
135
|
sentAt: DateTime!
|
|
130
136
|
}
|
|
137
|
+
|
|
138
|
+
input SenderInput {
|
|
139
|
+
id: ID! # Unique identifier for the sender
|
|
140
|
+
name: String
|
|
141
|
+
avatarUrl: URL # Allows us to pull the ENS and/or nft of the persons profile
|
|
142
|
+
}
|
|
131
143
|
```
|
|
132
144
|
|
|
133
145
|
7. Repeat step 6 for the other input operations based on the [Operations Schema](#operations-schema). If you noticed, you only need to add the name `(ADD_EMOJI_REACTION, EDIT_CHAT_NAME, etc)` of the operation without the `input` suffix. Then it will be generated once you press enter.
|
|
@@ -22,7 +22,7 @@ To write the opearation reducers of the `ChatRoom` document model, you need to g
|
|
|
22
22
|
To do this, run the following command in the terminal:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
ph generate ChatRoom.phdm.
|
|
25
|
+
ph generate ChatRoom.phdm.phd
|
|
26
26
|
```
|
|
27
27
|
|
|
28
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.
|
|
@@ -47,97 +47,95 @@ Open the `general-operations.ts` file and you should see the code that needs to
|
|
|
47
47
|
* - delete the file and run the code generator again to have it reset
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
export const reducer: ChatRoomAddMessageOperations = {
|
|
54
|
-
addMessageOperation(state, action, dispatch) {
|
|
55
|
-
if (action.input.content === "") {
|
|
56
|
-
throw new MessageContentCannotBeEmpty(); // Your reducer exception is used here as a custom error.
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
state.messages.push({
|
|
60
|
-
id: action.input.messageId,
|
|
61
|
-
content: action.input.content,
|
|
62
|
-
sender: action.input.sender,
|
|
63
|
-
sentAt: action.input.sentAt,
|
|
64
|
-
reactions: [],
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
addEmojiReactionOperation(state, action, dispatch) {
|
|
68
|
-
const message = state.messages.find(
|
|
69
|
-
(message) => message.id === action.input.messageId, // the reducer checks the existence of the message you want to react to.
|
|
70
|
-
);
|
|
50
|
+
import { MessageContentCannotBeEmpty } from "../../gen/general-operations/error.js";
|
|
51
|
+
import type { ChatRoomGeneralOperationsOperations } from "chatroom/document-models/chat-room";
|
|
71
52
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
53
|
+
export const chatRoomGeneralOperationsOperations: ChatRoomGeneralOperationsOperations =
|
|
54
|
+
{
|
|
55
|
+
addMessageOperation(state, action) {
|
|
56
|
+
if (action.input.content === "") {
|
|
57
|
+
throw new MessageContentCannotBeEmpty();
|
|
58
|
+
}
|
|
75
59
|
|
|
76
|
-
|
|
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,
|
|
77
|
+
);
|
|
78
|
+
if (!message) {
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
if (!message.reactions) {
|
|
83
|
+
message.reactions = [];
|
|
84
|
+
}
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (reaction.type === action.input.type) {
|
|
86
|
-
return {
|
|
87
|
-
...reaction,
|
|
88
|
-
reactedBy: [...reaction.reactedBy, action.input.reactedBy],
|
|
89
|
-
};
|
|
90
|
-
}
|
|
86
|
+
const existingReaction = message.reactions.find(
|
|
87
|
+
(r) => r.type === action.input.type,
|
|
88
|
+
);
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
reactedBy: [action.input.reactedBy], // if the message reaction doesn't exist yet a new reaction gets created
|
|
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({
|
|
99
96
|
type: action.input.type,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
reactedBy: [action.input.reactedBy],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
removeEmojiReactionOperation(state, action) {
|
|
102
|
+
const message = state.messages.find(
|
|
103
|
+
(m) => m.id === action.input.messageId,
|
|
104
|
+
);
|
|
105
|
+
if (!message) {
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
103
108
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (_message.id === message.id) {
|
|
107
|
-
return message;
|
|
109
|
+
if (!message.reactions) {
|
|
110
|
+
return;
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
state.messages = state.messages.map((message) => {
|
|
116
|
-
if (message.id === action.input.messageId) {
|
|
117
|
-
message.reactions = (message.reactions || []).map((reaction) => {
|
|
118
|
-
if (reaction.type === action.input.type) {
|
|
119
|
-
return {
|
|
120
|
-
...reaction,
|
|
121
|
-
reactedBy: reaction.reactedBy.filter(
|
|
122
|
-
(reactedBy) => reactedBy !== action.input.senderId, // We're removing the sender of the reaction from the the reactedBy object
|
|
123
|
-
),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return reaction;
|
|
128
|
-
});
|
|
113
|
+
const reactionIndex = message.reactions.findIndex(
|
|
114
|
+
(r) => r.type === action.input.type,
|
|
115
|
+
);
|
|
116
|
+
if (reactionIndex === -1) {
|
|
117
|
+
return;
|
|
129
118
|
}
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
120
|
+
const reaction = message.reactions[reactionIndex];
|
|
121
|
+
const userIndex = reaction.reactedBy.indexOf(action.input.senderId);
|
|
122
|
+
|
|
123
|
+
if (userIndex !== -1) {
|
|
124
|
+
reaction.reactedBy.splice(userIndex, 1);
|
|
125
|
+
|
|
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
|
+
};
|
|
138
|
+
|
|
141
139
|
```
|
|
142
140
|
|
|
143
141
|
## Write the Operation Reducers Tests
|
|
@@ -156,34 +154,38 @@ Here are the tests for the five operations written in the reducers file.
|
|
|
156
154
|
* - change it by adding new tests or modifying the existing ones
|
|
157
155
|
*/
|
|
158
156
|
|
|
159
|
-
import {
|
|
160
|
-
import {
|
|
161
|
-
|
|
162
|
-
import utils from "../../gen/utils";
|
|
157
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
158
|
+
import { generateId } from "document-model/core";
|
|
163
159
|
import {
|
|
164
|
-
|
|
160
|
+
reducer,
|
|
161
|
+
utils,
|
|
162
|
+
addMessage,
|
|
163
|
+
addEmojiReaction,
|
|
164
|
+
removeEmojiReaction,
|
|
165
|
+
editChatName,
|
|
166
|
+
editChatDescription,
|
|
167
|
+
} from "../../gen/index.js";
|
|
168
|
+
import type {
|
|
169
|
+
ChatRoomDocument,
|
|
165
170
|
AddMessageInput,
|
|
166
171
|
AddEmojiReactionInput,
|
|
167
172
|
RemoveEmojiReactionInput,
|
|
168
173
|
EditChatNameInput,
|
|
169
174
|
EditChatDescriptionInput,
|
|
170
|
-
} from "../../gen/
|
|
171
|
-
import { reducer } from "../../gen/reducer";
|
|
172
|
-
import * as creators from "../../gen/add-message/creators";
|
|
173
|
-
import { ChatRoomDocument } from "../../gen/types";
|
|
175
|
+
} from "../../gen/types.js";
|
|
174
176
|
|
|
175
|
-
describe("
|
|
177
|
+
describe("GeneralOperations Operations", () => {
|
|
176
178
|
let document: ChatRoomDocument;
|
|
177
179
|
|
|
178
180
|
beforeEach(() => {
|
|
179
181
|
document = utils.createDocument();
|
|
180
182
|
});
|
|
181
183
|
|
|
182
|
-
const
|
|
183
|
-
// This is a helper function for our upcoming
|
|
184
|
+
const addMessageHelper = (): [ChatRoomDocument, AddMessageInput] => {
|
|
185
|
+
// This is a helper function for our upcoming tests
|
|
184
186
|
const input: AddMessageInput = {
|
|
185
187
|
content: "Hello, World!",
|
|
186
|
-
messageId:
|
|
188
|
+
messageId: generateId(),
|
|
187
189
|
sender: {
|
|
188
190
|
id: "anon-user",
|
|
189
191
|
name: null,
|
|
@@ -192,17 +194,21 @@ describe("AddMessage Operations", () => {
|
|
|
192
194
|
sentAt: new Date().toISOString(),
|
|
193
195
|
};
|
|
194
196
|
|
|
195
|
-
const updatedDocument = reducer(document,
|
|
197
|
+
const updatedDocument = reducer(document, addMessage(input));
|
|
196
198
|
|
|
197
199
|
return [updatedDocument, input];
|
|
198
200
|
};
|
|
199
201
|
|
|
200
202
|
it("should handle addMessage operation", () => {
|
|
201
|
-
const [updatedDocument, input] =
|
|
203
|
+
const [updatedDocument, input] = addMessageHelper();
|
|
202
204
|
|
|
203
205
|
expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the message is being added to the operations history
|
|
204
|
-
expect(updatedDocument.operations.global[0].type).toBe(
|
|
205
|
-
|
|
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
|
+
);
|
|
206
212
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
207
213
|
|
|
208
214
|
expect(updatedDocument.state.global.messages).toHaveLength(1); // We're validating that the message is present in the message state of the document
|
|
@@ -217,7 +223,7 @@ describe("AddMessage Operations", () => {
|
|
|
217
223
|
|
|
218
224
|
it("should handle addEmojiReaction operation", () => {
|
|
219
225
|
// We're validating that we can react using an emoji with a helper function
|
|
220
|
-
const [doc, addMessageInput] =
|
|
226
|
+
const [doc, addMessageInput] = addMessageHelper();
|
|
221
227
|
|
|
222
228
|
let updatedDocument = doc;
|
|
223
229
|
|
|
@@ -229,14 +235,14 @@ describe("AddMessage Operations", () => {
|
|
|
229
235
|
|
|
230
236
|
updatedDocument = reducer(
|
|
231
237
|
updatedDocument,
|
|
232
|
-
|
|
238
|
+
addEmojiReaction(addEmojiReactionInput),
|
|
233
239
|
);
|
|
234
240
|
|
|
235
241
|
expect(updatedDocument.operations.global).toHaveLength(2); // We're validating that the emoji reaction is added to the operation history of the doc.
|
|
236
|
-
expect(updatedDocument.operations.global[1].type).toBe(
|
|
242
|
+
expect(updatedDocument.operations.global[1].action.type).toBe(
|
|
237
243
|
"ADD_EMOJI_REACTION",
|
|
238
244
|
);
|
|
239
|
-
expect(updatedDocument.operations.global[1].input).toStrictEqual(
|
|
245
|
+
expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
|
|
240
246
|
addEmojiReactionInput,
|
|
241
247
|
);
|
|
242
248
|
expect(updatedDocument.operations.global[1].index).toEqual(1);
|
|
@@ -251,28 +257,25 @@ describe("AddMessage Operations", () => {
|
|
|
251
257
|
});
|
|
252
258
|
|
|
253
259
|
it("should handle addEmojiReaction operation to a non existing message", () => {
|
|
254
|
-
// We're testing that
|
|
260
|
+
// We're testing that the state doesn't change when reacting to a non-existing message
|
|
255
261
|
const input: AddEmojiReactionInput = {
|
|
256
262
|
messageId: "invalid-message-id",
|
|
257
263
|
reactedBy: "anon-user",
|
|
258
264
|
type: "THUMBS_UP",
|
|
259
265
|
};
|
|
260
266
|
|
|
261
|
-
const updatedDocument = reducer(document,
|
|
267
|
+
const updatedDocument = reducer(document, addEmojiReaction(input));
|
|
262
268
|
|
|
263
269
|
expect(updatedDocument.operations.global).toHaveLength(1);
|
|
264
|
-
expect(updatedDocument.operations.global[0].type).toBe(
|
|
270
|
+
expect(updatedDocument.operations.global[0].action.type).toBe(
|
|
265
271
|
"ADD_EMOJI_REACTION",
|
|
266
272
|
);
|
|
267
|
-
expect(updatedDocument.operations.global[0].error).toBe(
|
|
268
|
-
"Message not found",
|
|
269
|
-
);
|
|
270
273
|
expect(updatedDocument.state.global.messages).toHaveLength(0);
|
|
271
274
|
});
|
|
272
275
|
|
|
273
276
|
it("should handle removeEmojiReaction operation", () => {
|
|
274
277
|
// We're making use of a helper function to check if we can remove an EmojiReaction
|
|
275
|
-
const [doc, addMessageInput] =
|
|
278
|
+
const [doc, addMessageInput] = addMessageHelper();
|
|
276
279
|
|
|
277
280
|
let updatedDocument = doc;
|
|
278
281
|
|
|
@@ -284,7 +287,7 @@ describe("AddMessage Operations", () => {
|
|
|
284
287
|
|
|
285
288
|
updatedDocument = reducer(
|
|
286
289
|
updatedDocument,
|
|
287
|
-
|
|
290
|
+
addEmojiReaction(addEmojiReactionInput),
|
|
288
291
|
);
|
|
289
292
|
|
|
290
293
|
const input: RemoveEmojiReactionInput = {
|
|
@@ -294,22 +297,19 @@ describe("AddMessage Operations", () => {
|
|
|
294
297
|
type: "THUMBS_UP",
|
|
295
298
|
};
|
|
296
299
|
|
|
297
|
-
updatedDocument = reducer(
|
|
298
|
-
updatedDocument,
|
|
299
|
-
creators.removeEmojiReaction(input),
|
|
300
|
-
);
|
|
300
|
+
updatedDocument = reducer(updatedDocument, removeEmojiReaction(input));
|
|
301
301
|
|
|
302
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].type).toBe(
|
|
303
|
+
expect(updatedDocument.operations.global[2].action.type).toBe(
|
|
304
304
|
"REMOVE_EMOJI_REACTION",
|
|
305
305
|
);
|
|
306
|
-
expect(updatedDocument.operations.global[2].input).toStrictEqual(
|
|
306
|
+
expect(updatedDocument.operations.global[2].action.input).toStrictEqual(
|
|
307
|
+
input,
|
|
308
|
+
);
|
|
307
309
|
expect(updatedDocument.operations.global[2].index).toEqual(2);
|
|
308
310
|
|
|
309
|
-
|
|
310
|
-
expect(
|
|
311
|
-
updatedDocument.state.global.messages[0].reactions?.[0]?.reactedBy,
|
|
312
|
-
).toHaveLength(0);
|
|
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
313
|
});
|
|
314
314
|
|
|
315
315
|
it("should handle editChatName operation", () => {
|
|
@@ -317,11 +317,15 @@ describe("AddMessage Operations", () => {
|
|
|
317
317
|
name: "New Chat Name",
|
|
318
318
|
};
|
|
319
319
|
|
|
320
|
-
const updatedDocument = reducer(document,
|
|
320
|
+
const updatedDocument = reducer(document, editChatName(input));
|
|
321
321
|
|
|
322
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].type).toBe(
|
|
324
|
-
|
|
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
|
+
);
|
|
325
329
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
326
330
|
|
|
327
331
|
expect(updatedDocument.state.global.name).toBe(input.name);
|
|
@@ -332,16 +336,15 @@ describe("AddMessage Operations", () => {
|
|
|
332
336
|
description: "New Chat Description",
|
|
333
337
|
};
|
|
334
338
|
|
|
335
|
-
const updatedDocument = reducer(
|
|
336
|
-
document,
|
|
337
|
-
creators.editChatDescription(input),
|
|
338
|
-
);
|
|
339
|
+
const updatedDocument = reducer(document, editChatDescription(input));
|
|
339
340
|
|
|
340
341
|
expect(updatedDocument.operations.global).toHaveLength(1); // We're validating that the operation is added to the operations history
|
|
341
|
-
expect(updatedDocument.operations.global[0].type).toBe(
|
|
342
|
+
expect(updatedDocument.operations.global[0].action.type).toBe(
|
|
342
343
|
"EDIT_CHAT_DESCRIPTION",
|
|
343
344
|
);
|
|
344
|
-
expect(updatedDocument.operations.global[0].input).toStrictEqual(
|
|
345
|
+
expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
|
|
346
|
+
input,
|
|
347
|
+
);
|
|
345
348
|
expect(updatedDocument.operations.global[0].index).toEqual(0);
|
|
346
349
|
|
|
347
350
|
expect(updatedDocument.state.global.description).toBe(input.description);
|