@powerhousedao/academy 5.1.0-dev.9 → 5.1.0-staging.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.
@@ -100,22 +100,20 @@ You will see that this action created a range of files for you. Before diving in
100
100
 
101
101
  ![Chatroom-demo Schema](image.png)
102
102
 
103
- Now you can navigate to `/document-models/chat-room/src/reducers/messages.ts` and start writing the operation reducers.
103
+ ## Implement the messages reducers
104
104
 
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:
105
+ Navigate to `/document-models/chat-room/src/reducers/messages.ts` and start writing the operation reducers for the messages module.
106
+
107
+ Open the `messages.ts` file and you should see the scaffolding code that needs to be filled for the three message operations. The generated file will look like this:
106
108
 
107
109
  ```typescript
108
- import type { ChatRoomMessagesOperations } from "chatroom/document-models/chat-room";
110
+ import type { ChatRoomMessagesOperations } from "@powerhousedao/chatroom-package/document-models/chat-room";
109
111
 
110
112
  export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
111
113
  addMessageOperation(state, action) {
112
114
  // TODO: Implement "addMessageOperation" reducer
113
115
  throw new Error('Reducer "addMessageOperation" not yet implemented');
114
116
  },
115
- senderOperation(state, action) {
116
- // TODO: Implement "senderOperation" reducer
117
- throw new Error('Reducer "senderOperation" not yet implemented');
118
- },
119
117
  addEmojiReactionOperation(state, action) {
120
118
  // TODO: Implement "addEmojiReactionOperation" reducer
121
119
  throw new Error('Reducer "addEmojiReactionOperation" not yet implemented');
@@ -127,20 +125,19 @@ export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
127
125
  };
128
126
  ```
129
127
 
130
- ## Write the operation reducers
128
+ ### Write the messages operation reducers
131
129
 
132
- 1. Copy and paste the code below into the `messages.ts` file in the `reducers` folder.
133
- 2. Save the file.
130
+ Copy and paste the code below into the `messages.ts` file in the `reducers` folder, replacing the scaffolding code:
134
131
 
135
132
  <details>
136
- <summary>Operation Reducers</summary>
133
+ <summary>Messages Operation Reducers</summary>
137
134
 
138
135
  ```typescript
139
136
  import {
140
137
  MessageNotFoundError,
141
138
  MessageContentCannotBeEmptyError,
142
139
  } from "../../gen/messages/error.js";
143
- import type { ChatRoomMessagesOperations } from "chatroom-package/document-models/chat-room";
140
+ import type { ChatRoomMessagesOperations } from "@powerhousedao/chatroom-package/document-models/chat-room";
144
141
 
145
142
  export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
146
143
  addMessageOperation(state, action) {
@@ -221,18 +218,20 @@ export const chatRoomMessagesOperations: ChatRoomMessagesOperations = {
221
218
  },
222
219
  };
223
220
  ```
221
+
224
222
  </details>
225
223
 
224
+ ## Implement the settings reducers
226
225
 
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.
226
+ Navigate to `/document-models/chat-room/src/reducers/settings.ts` and implement the settings operation reducers.
229
227
 
228
+ Copy and paste the code below into the `settings.ts` file in the `reducers` folder:
230
229
 
231
230
  <details>
232
- <summary>Operation Reducers</summary>
231
+ <summary>Settings Operation Reducers</summary>
233
232
 
234
233
  ```typescript
235
- import type { ChatRoomSettingsOperations } from "chatroom-package/document-models/chat-room";
234
+ import type { ChatRoomSettingsOperations } from "@powerhousedao/chatroom-package/document-models/chat-room";
236
235
 
237
236
  export const chatRoomSettingsOperations: ChatRoomSettingsOperations = {
238
237
  editChatNameOperation(state, action) {
@@ -246,231 +245,303 @@ export const chatRoomSettingsOperations: ChatRoomSettingsOperations = {
246
245
 
247
246
  </details>
248
247
 
249
-
250
248
  ## Write the operation reducer tests
251
249
 
252
250
  In order to make sure the operation reducers are working as expected, you need to write tests for them.
253
251
 
254
- Navigate to `/document-models/chat-room/src/tests` and replace the scaffolding code with comprehensive tests.
252
+ Navigate to `/document-models/chat-room/src/tests` and you'll find test files for each module. Replace the scaffolding code with the tests below.
253
+
254
+ ### Messages operation tests
255
255
 
256
- Here are the tests for the five operations implemented in the reducers file. The test file:
257
- - Uses Vitest for testing
258
- - Creates an empty ChatRoom document model
259
- - Tests add message, add/remove reactions, and edit operations
260
- - Verifies both the operation history and the resulting state
256
+ Replace the content of `messages.test.ts` with:
261
257
 
262
258
  <details>
263
- <summary>Operation Reducer Tests</summary>
259
+ <summary>Messages Operation Tests</summary>
264
260
 
265
261
  ```typescript
266
- import { describe, it, expect, beforeEach } from "vitest";
267
- import { generateId } from "document-model/core";
262
+ /**
263
+ * This is a scaffold file meant for customization:
264
+ * - change it by adding new tests or modifying the existing ones
265
+ */
266
+
267
+ import { describe, it, expect } from "vitest";
268
+ import { generateMock } from "@powerhousedao/codegen";
268
269
  import {
269
270
  reducer,
270
271
  utils,
272
+ isChatRoomDocument,
271
273
  addMessage,
274
+ AddMessageInputSchema,
272
275
  addEmojiReaction,
276
+ AddEmojiReactionInputSchema,
273
277
  removeEmojiReaction,
274
- editChatName,
275
- editChatDescription,
276
- } from "../../gen/index.js";
277
- import type {
278
- ChatRoomDocument,
279
- AddMessageInput,
280
- AddEmojiReactionInput,
281
- RemoveEmojiReactionInput,
282
- EditChatNameInput,
283
- EditChatDescriptionInput,
284
- } from "../../gen/types.js";
285
-
286
- describe("GeneralOperations Operations", () => {
287
- let document: ChatRoomDocument;
288
-
289
- beforeEach(() => {
290
- document = utils.createDocument();
291
- });
278
+ RemoveEmojiReactionInputSchema,
279
+ } from "@powerhousedao/chatroom-package/document-models/chat-room";
292
280
 
293
- // Helper function to add a message for testing
294
- const addMessageHelper = (): [ChatRoomDocument, AddMessageInput] => {
295
- const input: AddMessageInput = {
296
- content: "Hello, World!",
297
- messageId: generateId(),
298
- sender: {
299
- id: "anon-user",
300
- name: null,
301
- avatarUrl: null,
302
- },
303
- sentAt: new Date().toISOString(),
304
- };
281
+ describe("Messages Operations", () => {
282
+ it("should handle addMessage operation", () => {
283
+ const document = utils.createDocument();
284
+ const input = generateMock(AddMessageInputSchema());
305
285
 
306
286
  const updatedDocument = reducer(document, addMessage(input));
307
287
 
308
- return [updatedDocument, input];
309
- };
310
-
311
- // Test adding a new message
312
- it("should handle addMessage operation", () => {
313
- const [updatedDocument, input] = addMessageHelper();
314
-
315
- // Verify the operation was recorded
288
+ expect(isChatRoomDocument(updatedDocument)).toBe(true);
316
289
  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);
290
+ expect(updatedDocument.operations.global[0].action.type).toBe(
291
+ "ADD_MESSAGE",
292
+ );
293
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
294
+ input,
295
+ );
319
296
  expect(updatedDocument.operations.global[0].index).toEqual(0);
320
-
321
- // Verify the message was added to state
322
- expect(updatedDocument.state.global.messages).toHaveLength(1);
323
- expect(updatedDocument.state.global.messages[0]).toMatchObject({
324
- id: input.messageId,
325
- content: input.content,
326
- sender: input.sender,
327
- sentAt: input.sentAt,
328
- reactions: [],
329
- });
330
297
  });
331
-
332
- // Test adding an emoji reaction
333
298
  it("should handle addEmojiReaction operation", () => {
334
- const [doc, addMessageInput] = addMessageHelper();
335
-
336
- let updatedDocument = doc;
299
+ const document = utils.createDocument();
300
+ const input = generateMock(AddEmojiReactionInputSchema());
337
301
 
338
- const addEmojiReactionInput: AddEmojiReactionInput = {
339
- messageId: addMessageInput.messageId,
340
- reactedBy: "anon-user",
341
- type: "THUMBS_UP",
342
- };
302
+ const updatedDocument = reducer(document, addEmojiReaction(input));
343
303
 
344
- updatedDocument = reducer(
345
- updatedDocument,
346
- addEmojiReaction(addEmojiReactionInput),
304
+ expect(isChatRoomDocument(updatedDocument)).toBe(true);
305
+ expect(updatedDocument.operations.global).toHaveLength(1);
306
+ expect(updatedDocument.operations.global[0].action.type).toBe(
307
+ "ADD_EMOJI_REACTION",
347
308
  );
348
-
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");
352
- expect(updatedDocument.operations.global[1].action.input).toStrictEqual(
353
- addEmojiReactionInput,
309
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
310
+ input,
354
311
  );
355
- expect(updatedDocument.operations.global[1].index).toEqual(1);
356
-
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],
361
- type: addEmojiReactionInput.type,
362
- });
312
+ expect(updatedDocument.operations.global[0].index).toEqual(0);
363
313
  });
314
+ it("should handle removeEmojiReaction operation", () => {
315
+ const document = utils.createDocument();
316
+ const input = generateMock(RemoveEmojiReactionInputSchema());
364
317
 
365
- // Test adding reaction to non-existing message
366
- it("should handle addEmojiReaction operation to a non existing message", () => {
367
- const input: AddEmojiReactionInput = {
368
- messageId: "invalid-message-id",
369
- reactedBy: "anon-user",
370
- type: "THUMBS_UP",
371
- };
372
-
373
- const updatedDocument = reducer(document, addEmojiReaction(input));
318
+ const updatedDocument = reducer(document, removeEmojiReaction(input));
374
319
 
320
+ expect(isChatRoomDocument(updatedDocument)).toBe(true);
375
321
  expect(updatedDocument.operations.global).toHaveLength(1);
376
- expect(updatedDocument.operations.global[0].action.type).toBe("ADD_EMOJI_REACTION");
377
- expect(updatedDocument.state.global.messages).toHaveLength(0);
322
+ expect(updatedDocument.operations.global[0].action.type).toBe(
323
+ "REMOVE_EMOJI_REACTION",
324
+ );
325
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
326
+ input,
327
+ );
328
+ expect(updatedDocument.operations.global[0].index).toEqual(0);
378
329
  });
330
+ });
331
+ ```
379
332
 
380
- // Test removing an emoji reaction
381
- it("should handle removeEmojiReaction operation", () => {
382
- const [doc, addMessageInput] = addMessageHelper();
383
-
384
- let updatedDocument = doc;
385
-
386
- const addEmojiReactionInput: AddEmojiReactionInput = {
387
- messageId: addMessageInput.messageId,
388
- reactedBy: "anon-user",
389
- type: "THUMBS_UP",
390
- };
333
+ </details>
391
334
 
392
- updatedDocument = reducer(
393
- updatedDocument,
394
- addEmojiReaction(addEmojiReactionInput),
395
- );
335
+ ### Settings operation tests
396
336
 
397
- const input: RemoveEmojiReactionInput = {
398
- messageId: addMessageInput.messageId,
399
- senderId: "anon-user",
400
- type: "THUMBS_UP",
401
- };
337
+ Replace the content of `settings.test.ts` with:
402
338
 
403
- updatedDocument = reducer(updatedDocument, removeEmojiReaction(input));
339
+ <details>
340
+ <summary>Settings Operation Tests</summary>
404
341
 
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);
409
- expect(updatedDocument.operations.global[2].index).toEqual(2);
342
+ ```typescript
343
+ /**
344
+ * This is a scaffold file meant for customization:
345
+ * - change it by adding new tests or modifying the existing ones
346
+ */
410
347
 
411
- // Verify the reaction was removed
412
- expect(updatedDocument.state.global.messages[0].reactions).toHaveLength(0);
413
- });
348
+ import { describe, it, expect } from "vitest";
349
+ import { generateMock } from "@powerhousedao/codegen";
350
+ import {
351
+ reducer,
352
+ utils,
353
+ isChatRoomDocument,
354
+ editChatName,
355
+ EditChatNameInputSchema,
356
+ editChatDescription,
357
+ EditChatDescriptionInputSchema,
358
+ } from "@powerhousedao/chatroom-package/document-models/chat-room";
414
359
 
415
- // Test editing chat name
360
+ describe("Settings Operations", () => {
416
361
  it("should handle editChatName operation", () => {
417
- const input: EditChatNameInput = {
418
- name: "New Chat Name",
419
- };
362
+ const document = utils.createDocument();
363
+ const input = generateMock(EditChatNameInputSchema());
420
364
 
421
365
  const updatedDocument = reducer(document, editChatName(input));
422
366
 
423
- // Verify the operation was recorded
367
+ expect(isChatRoomDocument(updatedDocument)).toBe(true);
424
368
  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);
369
+ expect(updatedDocument.operations.global[0].action.type).toBe(
370
+ "EDIT_CHAT_NAME",
371
+ );
372
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
373
+ input,
374
+ );
427
375
  expect(updatedDocument.operations.global[0].index).toEqual(0);
428
-
429
- // Verify the name was updated
430
- expect(updatedDocument.state.global.name).toBe(input.name);
431
376
  });
432
-
433
- // Test editing chat description
434
377
  it("should handle editChatDescription operation", () => {
435
- const input: EditChatDescriptionInput = {
436
- description: "New Chat Description",
437
- };
378
+ const document = utils.createDocument();
379
+ const input = generateMock(EditChatDescriptionInputSchema());
438
380
 
439
381
  const updatedDocument = reducer(document, editChatDescription(input));
440
382
 
441
- // Verify the operation was recorded
383
+ expect(isChatRoomDocument(updatedDocument)).toBe(true);
442
384
  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);
385
+ expect(updatedDocument.operations.global[0].action.type).toBe(
386
+ "EDIT_CHAT_DESCRIPTION",
387
+ );
388
+ expect(updatedDocument.operations.global[0].action.input).toStrictEqual(
389
+ input,
390
+ );
445
391
  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
+
405
+ ```typescript
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
+ });
446
431
 
447
- // Verify the description was updated
448
- expect(updatedDocument.state.global.description).toBe(input.description);
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
+ }
449
448
  });
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
+ }
450
518
  });
451
519
  ```
452
520
 
453
521
  </details>
454
522
 
523
+ ## Run the tests
524
+
455
525
  Now you can run the tests to make sure the operation reducers are working as expected.
456
526
 
457
527
  ```bash
458
528
  pnpm run test
459
529
  ```
460
530
 
461
- Output should be as follows:
531
+ Output should be similar to:
462
532
 
463
533
  ```bash
464
534
  ✓ 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
535
+ ✓ document-models/chat-room/src/tests/messages.test.ts (3 tests) 8ms
536
+ ✓ document-models/chat-room/src/tests/settings.test.ts (2 tests) 2ms
466
537
 
467
- Test Files 2 passed (2)
468
- Tests 9 passed (9)
538
+ Test Files 3 passed (3)
539
+ Tests 8 passed (8)
469
540
  Start at 15:19:52
470
541
  Duration 3.61s (transform 77ms, setup 0ms, collect 3.50s, tests 14ms, environment 0ms, prepare 474ms)
471
542
  ```
472
543
 
473
- If you got the same output, you have successfully implemented the operation reducers and tests for the **ChatRoom** document model.
544
+ If you got a similar output, you have successfully implemented the operation reducers and tests for the **ChatRoom** document model.
474
545
 
475
546
  ## Compare with reference implementation
476
547
 
@@ -478,10 +549,12 @@ Verify your implementation matches the tutorial:
478
549
 
479
550
  ```bash
480
551
  # View reference reducer implementation
481
- git show tutorial/main:document-models/chat-room/src/reducers/general-operations.ts
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
482
554
 
483
555
  # View reference test implementation
484
- git show tutorial/main:document-models/chat-room/src/tests/general-operations.test.ts
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
485
558
 
486
559
  # Compare your entire implementation
487
560
  git diff tutorial/main -- document-models/chat-room/src/