@powerhousedao/academy 5.0.1-staging.14 → 5.0.1-staging.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
  3. package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +1 -1
  4. package/docs/academy/01-GetStarted/05-VetraStudio.md +76 -27
  5. package/docs/academy/01-GetStarted/home.mdx +4 -4
  6. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/01-Prerequisites.md +24 -8
  7. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/02-StandardDocumentModelWorkflow.md +25 -7
  8. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +17 -3
  9. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/01-WhatIsADocumentModel.md +5 -2
  10. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +7 -1
  11. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +5 -5
  12. package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +4 -1
  13. package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +21 -9
  14. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +132 -129
  15. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +50 -44
  16. package/docs/academy/03-ExampleUsecases/Chatroom/image-4.png +0 -0
  17. package/docs/academy/03-ExampleUsecases/Chatroom/image-5.png +0 -0
  18. package/docs/academy/03-ExampleUsecases/Chatroom/images/ChatRoomConnectApp.gif +0 -0
  19. package/docs/academy/03-ExampleUsecases/Chatroom/images/ChatRoomTest.gif +0 -0
  20. package/docs/academy/05-Architecture/01-WorkingWithTheReactor.md +0 -2
  21. package/package.json +1 -1
  22. package/sidebars.ts +8 -0
  23. package/src/css/custom.css +20 -0
  24. package/static/img/ethereum-logo.jpeg +0 -0
  25. package/docs/academy/09-AIResources +0 -131
  26. package/docs/academy/TUTORIAL_VERIFICATION_ARCHITECTURE +0 -325
  27. /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline.md → 02-RevisionHistoryTimeline} +0 -0
@@ -10,13 +10,13 @@ Please start with the '**Get Started**' Chapter or '**Document Model Creation**'
10
10
  <details>
11
11
  <summary>Key commands that you'll use in this flow</summary>
12
12
 
13
- - `pnpm install -g ph-cmd`: Installs the Powerhouse CLI globally.
13
+ - `pnpm install -g ph-cmd` or `npm install -g ph-cmd`: Installs the Powerhouse CLI globally.
14
14
  - `ph init`: Initializes a new Powerhouse project or sets up the local environment.
15
15
  - `ph connect`: Runs Connect in Studio Mode for local development and testing.
16
16
  - `ph generate <YourModelName.phdm.zip>`: Generates scaffolding code from an exported document model specification.
17
17
  - `ph generate --editor YourModelName --document-types powerhouse/YourModelName`: Generates an editor template for a document model.
18
- - `pnpm build`: Builds the project for production.
19
- - `pnpm run test`: Runs unit tests.
18
+ - `pnpm build` or `npm run build`: Builds the project for production.
19
+ - `pnpm run test` or `npm test`: Runs unit tests.
20
20
  - `npm login`: Logs into your NPM account.
21
21
  - `npm publish`: Publishes your package to NPM.
22
22
  - `ph install @your-org-ph/your-package-name`: Installs a published package into a local Powerhouse environment.
@@ -33,8 +33,14 @@ Ensure you have the Powerhouse Command Line Interface (`ph-cmd`) installed. This
33
33
  pnpm install -g ph-cmd
34
34
  ```
35
35
 
36
+ Or if you're using npm:
37
+
38
+ ```bash
39
+ npm install -g ph-cmd
40
+ ```
41
+
36
42
  :::info **Prerequisites**
37
- The [Prerequisites](/academy/MasteryTrack/BuilderEnvironment/Prerequisites) guide for detailed installation instructions for Node.js, pnpm, and Git if you haven't set them up yet.
43
+ See the [Prerequisites](/academy/MasteryTrack/BuilderEnvironment/Prerequisites) guide for detailed installation instructions for Node.js 22, package managers (pnpm or npm), and Git if you haven't set them up yet.
38
44
  :::
39
45
 
40
46
  ### 1.2. Initialize your project environment
@@ -54,9 +60,9 @@ When installing or using the Powerhouse CLI commands you are able to make use of
54
60
 
55
61
  | Command | Description |
56
62
  | ---------------------------------- | ----------------------------------------------------- |
57
- | **pnpm install -g ph-cmd** | Install latest stable version |
58
- | **pnpm install -g ph-cmd@dev** | Install development version |
59
- | **pnpm install -g ph-cmd@staging** | Install staging version |
63
+ | **pnpm install -g ph-cmd** or **npm install -g ph-cmd** | Install latest stable version |
64
+ | **pnpm install -g ph-cmd@dev** or **npm install -g ph-cmd@dev** | Install development version |
65
+ | **pnpm install -g ph-cmd@staging** or **npm install -g ph-cmd@staging** | Install staging version |
60
66
  | **ph init** | Use latest stable version of the boilerplate |
61
67
  | **ph init --dev** | Use development version of the boilerplate |
62
68
  | **ph init --staging** | Use staging version of the boilerplate |
@@ -143,6 +149,12 @@ It's crucial to test your reducer logic. Write unit tests in the `document-model
143
149
  pnpm run test
144
150
  ```
145
151
 
152
+ Or with npm:
153
+
154
+ ```bash
155
+ npm test
156
+ ```
157
+
146
158
  ### 3.4. Implement the document editor
147
159
 
148
160
  A document editor provides the user interface for interacting with your document model in Connect. Generate an editor template:
@@ -262,6 +274,12 @@ Compile and optimize your project for production:
262
274
  pnpm build
263
275
  ```
264
276
 
277
+ Or with npm:
278
+
279
+ ```bash
280
+ npm run build
281
+ ```
282
+
265
283
  This command typically creates a `dist/` or `build/` directory with the compiled assets. Ensure your `package.json`'s `files` array includes this directory and other necessary assets like `manifest.json`, `document-models`, and `editors` if they are not part of the build output but need to be in the package.
266
284
 
267
285
  ### 4.5. Version control
@@ -20,6 +20,12 @@ The Powerhouse CLI (`ph-cmd`) is a command-line interface tool that provides ess
20
20
  pnpm install -g ph-cmd
21
21
  ```
22
22
 
23
+ Or if you're using npm:
24
+
25
+ ```bash
26
+ npm install -g ph-cmd
27
+ ```
28
+
23
29
  Key commands include:
24
30
 
25
31
  - `ph connect` for running the Connect application locally
@@ -36,9 +42,9 @@ When installing or using the Powerhouse CLI commands you are able to make use of
36
42
 
37
43
  | Command | Description |
38
44
  | ---------------------------------- | ----------------------------------------------------- |
39
- | **pnpm install -g ph-cmd** | Install latest stable version |
40
- | **pnpm install -g ph-cmd@dev** | Install development version |
41
- | **pnpm install -g ph-cmd@staging** | Install staging version |
45
+ | **pnpm install -g ph-cmd** or **npm install -g ph-cmd ** | Install latest stable version |
46
+ | **pnpm install -g ph-cmd@dev** or **npm install -g ph-cmd@dev** | Install development version |
47
+ | **pnpm install -g ph-cmd@staging** or **npm install -g ph-cmd@staging** | Install staging version |
42
48
  | **ph init** | Use latest stable version of the boilerplate |
43
49
  | **ph init --dev** | Use development version of the boilerplate |
44
50
  | **ph init --staging** | Use staging version of the boilerplate |
@@ -62,6 +68,8 @@ If you need to perform a clean reinstallation of the Powerhouse CLI (`ph-cmd`),
62
68
 
63
69
  ```bash
64
70
  pnpm uninstall -g ph-cmd
71
+ # or with npm
72
+ npm uninstall -g ph-cmd
65
73
  ```
66
74
 
67
75
  2. Remove the Powerhouse configuration directory:
@@ -75,12 +83,18 @@ rm -rf ~/.ph
75
83
  ```bash
76
84
  # For the latest stable version
77
85
  pnpm install -g ph-cmd
86
+ # or with npm
87
+ npm install -g ph-cmd
78
88
 
79
89
  # For the staging version
80
90
  pnpm install -g ph-cmd@staging
91
+ # or with npm
92
+ npm install -g ph-cmd@staging
81
93
 
82
94
  # For a specific version
83
95
  pnpm install -g ph-cmd@<version>
96
+ # or with npm
97
+ npm install -g ph-cmd@<version>
84
98
  ```
85
99
 
86
100
  This process ensures a clean slate by removing both the CLI tool and its configuration files before installing the desired version. It's particularly useful when:
@@ -1,8 +1,11 @@
1
1
  # What is a document model?
2
2
 
3
3
  :::tip
4
- This chapter on **Document Model Creation** will help you with an in depth practicial understanding while building an **advanced to-do list** document model.
5
- Although not required, if you have completed the 'Get Started' tutorial you will revisit familiar topics and can update your existing document model.
4
+ This chapter on **Document Model Creation** will help you with an in-depth practical understanding while building an **advanced to-do list** document model.
5
+
6
+ If you have completed the [Get Started](/academy/GetStarted/CreateNewPowerhouseProject) tutorial (which includes creating a simple to-do list document model), you will revisit familiar topics and can enhance your existing document model with the advanced features covered in this Mastery Track, such as statistics tracking.
7
+
8
+ Although not required, completing the Get Started tutorial first is highly recommended as it provides a hands-on foundation for the concepts explored in depth here.
6
9
  :::
7
10
 
8
11
  :::info **Definition: What is a Document Model?**
@@ -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
 
@@ -2,16 +2,16 @@
2
2
 
3
3
  :::info
4
4
 
5
- The Todo-demo-package 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.
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-package
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-package
12
+ ### Option 1: Rebuild the Todo-demo
13
13
 
14
- The Todo-demo-package and repository are your main reference points during the Mastery Track.
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-package
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
- Create a new document model by clicking on the `DocumentModel` button by the "New Document" section. The Gif below shows you where to click.
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
  ![Create New Document Model](./images/ChatRoomConnectApp.gif)
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: Sender! # ID of the user sending the message
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
- ```graphql
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! # ID of the message that is being added
127
- sender: Sender! # ID of the user sending the message
128
- content: String! # Content of the message
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.zip
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 { ChatRoomAddMessageOperations } from "../../gen/add-message/operations";
51
- import { MessageContentCannotBeEmpty } from "../../gen/add-message/error";
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
- if (!message) {
73
- throw new Error("Message not found");
74
- }
53
+ export const chatRoomGeneralOperationsOperations: ChatRoomGeneralOperationsOperations =
54
+ {
55
+ addMessageOperation(state, action) {
56
+ if (action.input.content === "") {
57
+ throw new MessageContentCannotBeEmpty();
58
+ }
75
59
 
76
- const reactions = message.reactions || [];
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
- const existingReaction = reactions.find(
79
- (reaction) => reaction.type === action.input.type,
80
- );
82
+ if (!message.reactions) {
83
+ message.reactions = [];
84
+ }
81
85
 
82
- if (existingReaction) {
83
- // if the message reaction exists a new reactedBy gets added.
84
- message.reactions = reactions.map((reaction) => {
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
- return reaction;
93
- });
94
- } else {
95
- message.reactions = [
96
- ...reactions,
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
- state.messages = state.messages.map((_message) => {
105
- // the state of the chatroom documents messages gets updated
106
- if (_message.id === message.id) {
107
- return message;
109
+ if (!message.reactions) {
110
+ return;
108
111
  }
109
112
 
110
- return _message;
111
- });
112
- },
113
- removeEmojiReactionOperation(state, action, dispatch) {
114
- // To remove a reaction the address is removed from the reactedBy object in the reactions type.
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
- return message; // We're updating the document state with our changes
132
- });
133
- },
134
- editChatNameOperation(state, action, dispatch) {
135
- state.name = action.input.name || "";
136
- },
137
- editChatDescriptionOperation(state, action, dispatch) {
138
- state.description = action.input.description || "";
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 { generateMock } from "@powerhousedao/codegen";
160
- import { utils as documentModelUtils } from "document-model/document";
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
- z,
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/schema";
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("AddMessage Operations", () => {
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 addMessage = (): [ChatRoomDocument, AddMessageInput] => {
183
- // This is a helper function for our upcoming test
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: documentModelUtils.hashKey(),
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, creators.addMessage(input));
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] = addMessage();
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("ADD_MESSAGE");
205
- expect(updatedDocument.operations.global[0].input).toStrictEqual(input);
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] = addMessage();
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
- creators.addEmojiReaction(addEmojiReactionInput),
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 an error is thrown when reacting to a non-existing message
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, creators.addEmojiReaction(input));
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] = addMessage();
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
- creators.addEmojiReaction(addEmojiReactionInput),
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(input);
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
- expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(1); // The reaction should still exist but no-one should have reacted to it
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, creators.editChatName(input));
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("EDIT_CHAT_NAME");
324
- expect(updatedDocument.operations.global[0].input).toStrictEqual(input);
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(input);
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);