Individual Project 2 Due Wednesday October 19, 11:00am ET
Welcome back! We were pleased to see your thorough implementation of the new Interactables abstraction in the townService of Covey.Town. While you have been working on these backend features, our UX designer Calin has finished the design of the ViewingAreas feature, and we’re now ready to give you the rest of the feature. Calin found the react-player component, which seems to be a great component to use to implement video playback within Covey.Town.
In this (final) deliverable for the ViewingAreas feature, you will create the following components:
- Within the backend townService: REST and socket-io endpoints to process API calls from clients, dispatching them to the controllers delivered in IP1.
- Within the frontend application:
- An event handler for the frontend
TownController
to processinteractableUpdate
messages dispatched by thetownService
(you implemented those backend components in IP1). - A
ViewingAreaController
andConversationAreaController
that will maintain data structures and dispatch events to the UI in response to updates from theTownController
- React hooks to update existing React components in response to changes to
ViewingAreas
,ConversationAreas
, and town settings - A React component for the
ViewingArea
that integrates thereact-player
with those hooks and controllers
- An event handler for the frontend
When you complete this deliverable, you should have a fully-functioning implementation of the Viewing Area feature, and a better understanding of the covey.town architecture.
This sequence diagram shows the interaction between these high level components to create a new viewing area and play a video back synchronized between multiple frontends.
The sequence beings when a user selects a video to play in a viewing area, entering it into the ViewingAreaVideo
:
- The
ViewingAreaVideo
asks theTownController
to create a new viewing area with the specified video - The
TownController
asks thetownService
to create the new viewing area with the specified video, making a REST call - Assuming that the request was valid, the
townService
returns success - Assuming that the request was valid, the
TownController
returns success - The
townService
broadcasts aninteractableUpdate
message with the new video URL (happening in parallel with 9) - The
TownController
receives theinteractableUpdate
, finds the correctViewingAreaController
and pushes anupdateModel
event to it - The
ViewingAreaController
updates its model, and emits avideoChange
event to its listeners - The
ViewingAreaVideo
receives the update, re-renders, and now plays the video - The
townService
sends the sameinteractableUpdate
to Calin’s frontend, and (6-8) happen in Calin’s frontend - During playback, Avery’s ViewingAreaVideo updates the
elapsedTimeSec
on theViewingAreaController
- In response to the update from the
ViewingAreaVideo
, theViewingAreaController
asks theTownController
to emit an update to thetownService
- The
TownController
emits theinteractableUpdate
event, notifying the backend of the elapsed playack time - The
townService
relays thatinteractableUpdate
to other clients, which will ensure that their video playback is synchronized - In parallel to 10-13, Calin’s client emits
interactableUpdate
updates with theirelapsedTimeSec
- The
townService
forwards this update to Avery’s frontendTownController
- Avery’s
TownController
finds theViewingAreaController
responsible for that viewing area, and calls itsupdateModel
method, updating its view of theelapsedTimeSec
- The
ViewingAreaController
emits aprogressChange
event to its listeners. - The
ViewingAreaVideo
will seek to the newelapsedTimeSec
if it is out of sync
Objectives of this assignment
The objectives of this assignment are to:
- Write new TypeScript code that uses asynchronous operations
- Write test cases that utilize mocks and spies
- Write React components and hooks that make use of state
Getting started with this assignment
Start by downloading the starter code. Extract the archive and run npm install
to fetch the dependencies.
Installation notes
Configuring Jest and VSCode: If you would like to use the built-in Jest test runner for VSCode (where it shows the tests and their status in the sidebar), the easiest way to accomplish this for this project is to open just the “frontend” directory or just the “townService” directory in VSCode - not the top-level “ip2-handout” directory. If you have a quick-fix to make it work with the whole project at once, please feel free to share on Piazza and we will incorportate that here.
NPM install failures: The libraries used for React require some native binaries to be installed – code written and compiled for your computer (not JavaScript). If you run into issues with npm install
not succeeding, please try installing the following libraries using either Homebrew (if on Mac), apt-get, or your favorite other package manager: pixman
, cairo
, pkgconfig
and pango
. For example, run brew install pixman cairo pkgconfig pango
. If you are on a newer Mac with an M1 or M2 chip, you may need to use arch -arm64 brew install pixman cairo pango
. On Windows: Students have reported seeing the failure error /bin/bash: node: command not found
upon npm install
in the frontend
directory. If you encounter this error, please try to delete the node_modules
directory and re-run npm install
in the frontend
directory from a bash shell instead of a windows command prompt.
Changelog:
- 9/26: Update description for
socket.on('interactableUpdate')
on this page to match the handout; update the handout to remove prettier/linting issues (if you already have downloaded the handout,npm run format
in the frontend directory will get you the same thing) - 9/27: Clarify that
socket.on('interactableUpdate')
should forward the interactableUpdate message to the other players in the town using the emitternewPlayer.townEmitter
. Update handout so thatViewingAreaController
getter forvideo
return type isstring | undefined
. Add installation suggestions. - 9/29: Added sequence diagram of hook that uses town events.
Grading
This submission will be scored out of 200 points, 180 of which will be automatically awarded by the grading script, with the remaining 20 manually awarded by the course staff.
Your code will automatically be evaluated for linter errors and warnings. Submissions that have any linter errors will automatically receive a grade of 0. Do not wait to run the linter until the last minute. To check for linter errors, run the command npm run lint
from the terminal. The handout contains the same eslint configuration that is used by our grading script.
Your code will be automatically evaluated for functional correctness by a test suite that expands on the core tests that are distributed in the handout. Your tests will be automatically evaluated for functional correctness by a process that will inject bugs into our reference solution: to receive full marks your tests must detect a minimum number of injected bugs. You will not receive detailed feedback on which injected bugs you do or do not find.
The autograding script will impose a strict rate limit of 5 submissions per 24 hours. Submissions that fail to grade will not count against the quota. This limit exists to encourage you to start early on this assignment: students generally report that assignments like this take between 10-36 hours. If you start early, you will be able to take full advantage of the resources that we provide to help you succeed: office hours, discussion on Piazza — and the ability to have a greater total number of submission attempts.
Your code will be manually evaluated for conformance to our course style guide. This manual evaluation will account for 10% of your total grade on this assignment. We will manually evaluate your code for style on the following rubric:
To receive all 20 points:
- All new names (e.g. for local variables, methods, and properties) follow the naming conventions defined in our style guide
- There are no unused local variables
- All public properties and methods (other than getters, setters, and constructors) are documented with JSDoc-style comments that describes what the property/method does, as defined in our style guide
- The code and tests that you write generally follows the design principles discussed in week one. In particular, your design does not have duplicated code that could have been refactored into a shared method.
We will review your code and note each violation of this rubric. We will deduct four points for each violation, up to a maximum of deducting all 20 style points.
Implementation Tasks
This deliverable has four parts; each part will be graded on its own rubric. You should complete the assignment one part at a time, in the order presented here:
Task 1: Implement Backend Handlers (15 points total)
In your last deliverable for the indivdiual project, you implemented a considerable portion of the backend code to support Interactables. What remains are the public-facing web service APIs that the client can directly invoke.
These methods are located in two files:
- townService/src/town/Town.ts (
socket.on('interactableUpdate')
handler andaddViewingArea
) - townService/src/town/TownsController.ts (
createViewingArea
)
The socket.on
handler is automatically invoked by the socket-io library when an event is received from a remote client.
The createViewingArea
function is automatically invoked by the tsoa REST middleware when a REST request is made by a remote client.
We will learn more about both technologies in modules 9 and 10; for the purposes of this assignment you need only implement the functions as specified (such that they pass the provided test cases).
To run the tests for this part, run the command npm test TestName
in the townService
directory, where TestName
is either Town.test
or TownsController
.
Clarification (9/24): A viewing area is “active” if there is a video set.
View the specification for these tasks
Grading for Task 1:
You do not need to write any tests for task 1. The handout contains all of the tests that our grading script will use.
Point break down for each of the implementation tasks:
- Implement Town.ts socket.on(‘interactableUpdate’): 5 points
- Implement Town.ts addViewingArea: 5 points
- Implement TownController.ts createViewingArea: 5 points
To receive marks for implementing each feature, your implementation must pass all of our tests for it.
Task 2: Implement and Test Frontend Controllers (65 points total)
Similar to the organization of the backend townService, the frontend application also has controllers that maintain the state of each interactable.
The relevant files for this task are located in the directory frontend/src/classes/
.
The TownController
interacts with the townService
, receiving ServerToClientEvents
from the backend and emitting ClientToServerEvents
to the backend.
The TownController
, in turn, emits TownEvents
to components in the frontend. These events are the events that the GUI components will observe. Each Viewing Area is represented by a ViewingAreaController
, which emits ViewingAreaEvents
. Each Conversation Area is represented by a ConversationAreaController
, which emits ConversationAreaEvents
. GUI components that display details about each converation area or viewing area will subscribe to these events so that they can remain up-to-date with the current state of the interactable.
Your next task is to implement the ViewingAreaController
and ConversationAreaController
, along with the event handler for TownController
to receive interactableUpdate
messages from the townService.
Each of these classes are stubbed out in the handout.
Our handout does not include all of the tests in ViewingAreaController.test.ts
or ConversationAreaController.test.ts
. To receive full marks on task 2, you will also need enhance these test suites to check all of the behaviors of the methods that you are implementing.
Testing the behavior of the ViewingAreaController
and ConversationAreaController
will require you to use mocks.
The ViewingAreaController.test.ts
and ConversationAreaController.test.ts
files in the handout contain all of the setup code that you will need to write tests to check that the correct listeners are invoked.
The mockListeners
object (in each test) are mock objects, which do not provide any implementation of the listener callbacks, but keep track of when they have been called.
In this way, you can write an assertion that some listener method is called by asserting that the mock listener was called.
To write an assertion that, for example, the occupantsChange
listener is invoked in ConversationAreaController
, you could use Jest’s toHaveBeenCalled() matcher, as in:
expect(mockListeners.occupantsChange).toHaveBeenCalled()
. You might also find it useful to use the toHaveBeenCalledWith(args..) matcher to check the arguments that are passed to the listener.
To assert that a listener was not called, chain the not
matcher, as in expect(...).not.toHaveBeenCalled()
.
We strongly suggest writing the tests before (or concurrent) with implementing the classes, so that you can use your own tests to help you develop your implementation.
Note: you may find it useful to use the helper methods isConversationArea
and isViewingArea
, defined in TypeUtils.ts
To run the tests for this part, run the command npm test TestName
in the frontend
directory, where TestName
is either ViewingAreaController
, ConversationAreaController
, or TownController
.
View the specification for these tasks
ConversationAreaController:
ViewingAreaController
TownController socket.on('interactableUpdate')
Grading for Task 2:
Point break down for each of the implementation tasks:
- Implement ConversationAreaController: 7 points
- Implement ViewingAreaController: 7 points
- Implement TownController.interactableUpdate: 6 points
To receive marks for implementing each feature, your implementation must pass all of our tests for it.
Point break down for each of the testing tasks:
- Test ConversationAreaController occupants property: 7 points
- Test ConversationAreaController topic property: 7 points
- Test ConversationAreaController isEmpty: 7 points
- Test ConversationAreaController toConversationAreaModel: 2 points
- Test ViewingAreaController video property: 7 points
- Test ViewingAreaController elapsedTimeSec property: 7 points
- Test ViewingAreaController isPlaying property: 7 points
- Test ViewingAreaController viewingAreaModel: 1 points
Partial marks are available for detecting some (but not all) faults. The number of faults detected may not directly correlate with the difficulty of writing the test: there are several faults that are nearly guaranteed to be detected together (writing a test that finds one of them is guaranteed to find both of them), which is why there are different cutoffs for partial and full marks for the tests.
Task 3: Implement React Hooks (60 points total)
As discussed in Module 8, an effective pattern for building React applications is to use hooks within components to access global state. As part of the refactoring to implement the Interactable abstraction throughout Covey.Town, Avery also refactored the entire React-based frontend to use this pattern of hooks. Before implementing the final component that displays and synchronizes video playback in Viewing Areas, your next task will be to implement these hooks - some of which are related to the interactables, and some of which are related to Avery’s overall refactoring to use more hooks.
Some of these hooks may require you to include useEffect
and/or useState
hooks within the hook that you are building.
For each of the hooks, consider the events that they might need to listen to (i.e. TownEvents
for the hooks that monitor a TownController
and ConversationAreaEvents
for those that monitor a ConversationAreaController
).
Hooks that need to monitor TownController
events may require you to use our own useTownController()
hook, which returns the current TownController
.
The sequence diagram below shows the expected interactions between hooks that subscribe to TownEvents
, indicating the interfaces that the hook uses:
Be sure to follow the rules of hooks when implementing your hooks - these will be enforced by the linter, and also by the TAs when grading for style.
To run the tests for this part, run the command npm test hooks
in the frontend
directory.
View the specification for the hooks
In frontend/src/classes/TownController.ts
In frontend/src/classes/ConversationAreaController.ts
:
There are no hooks for the ViewingAreaController
at this point - Avery noticed that the data in the ViewingAreaController
is only used by at most one component, so felt that it would be an over-eager design optimization to define reusable hooks to access that data now.
Grading for Task 3:
You do not need to write any tests for task 1. The handout contains all of the tests that our grading script will use.
Point break down for each of the implementation tasks:
- Implement TownController.ts useTownSettings: 12 points
- Implement TownController.ts useActiveConversationAreas: 12 points
- Implement TownController.ts usePlayers: 12 points
- Implement ConversationAreaController.ts useConversationAreaOccupants: 12 points
- Implement ConversationAreaController.ts useConversationAreaTopic: 12 points
To receive marks for implementing each feature, your implementation must pass all of our tests for it.
Task 4: GUI Component for Viewing Area Videos (40 points)
With the controllers implemented, the last task will be to implement the frontend GUI component to play back videos in a Viewing Area. Avery has implemented the skeleton for this component, which also includes a form to set the video for a viewing area if it hasn’t already been set.
Your task is to implement the component ViewingAreaVideo
, which renders the viewing area’s video and synchronizes playback with the ViewingAreaController
. You will find that there is already a skeleton of this component created, which renders a <ReactPlayer>
component inside of a <Container>
, along with the ID of the ViewingArea
.
In addition to useState
and useEffect
, this component will also need to make use of React’s useRef
hook. useRef
is used to make a reference to a child component - the React documentation shows how useRef
is used to make a reference to a text input that can be used from within an event handler.
The handout code contains the declaration of a useRef
hook and ties that reference to the ReactPlayer
component. You will find it necessary to reference the ReactPlayer
component in order to retrieve its current playback timecode, and to seek to a new timecode.
To run the tests for this part, run the command npm test ViewingAreaVideo
in the frontend
directory.
The specification for this component is provided in comments in the file frontend/src/components/Town/interactables/ViewingAreaVideo.tsx
, and reproduced below:
View the specification for this component
Hints: The playing
property of the ReactPlayer
will need to be changed during the component’s lifetime: set to false
when another player pauses the video, or true
when it is resumed. This might be a good use-case for useState
. In contrast, seeking the video to a new timecode is accomplished not by changing a property, but by using the instance method seekTo
of the ReactPlayer. The handout code already has a useRef()
hook to demonstrate how to get a reference to the component.
Grading for Task 4:
You do not need to write any tests for task 4. The handout contains all of the tests that our grading script will use.
Point break down for each of the implementation tasks:
- Implement ViewingAreaVideo - Set properties of the ReactPlayer: 6 points
- Implement ViewingAreaVideo - Bridging events from the ViewingAreaController to the ReactPlayer: 17 points
- Implement ViewingAreaVideo - Bridging events from the ReactPlayer to the ViewingAreaController: 17 points
To receive marks for implementing each feature, your implementation must pass all of our tests for it.
Submission Instructions
Submit your assignment in GradeScope. The easiest way to get into GradeScope the first time is to first sign into Canvas and then click the link on our course for “GradeScope”. You should then also have the option to create an account on GradeScope (if you don’t already have one) so that you can log in to GradeScope directly. Please contact the instructors immediately if you have difficulty accessing the course on GradeScope.
To submit your assignment: run the command npm run zip
in the top-level directory of the handout. This will produce a file called covey-town.zip
which should contain these files:
- townService/src/town/Town.ts
- townService/src/town/TownsController.ts
- frontend/src/classes/TownController.ts
- frontend/src/classes/ConversationAreaController.ts
- frontend/src/classes/ConversationAreaController.test.ts
- frontend/src/classes/ViewingAreaController.ts
- frontend/src/classes/ViewingAreaController.test.ts
- frontend/src/components/Town/interactables/ViewingAreaVideo.tsx
GradeScope will provide you with feedback on your submission, but note that it will not include any marks that will be assigned after we manually grade your submission for code style (it will show 0 for this until it is graded). It may take several minutes for the grading script to complete.
GradeScope is configured to only provide feedback on at most 5 submissions per-24-hours per-student (submissions that fail to run or receive a grade of 0 are not counted in that limit). We strongly encourage you to lint and test your submission on your local development machine, and not rely on GradeScope for providing grading feedback - relying on GradeScope is a very slow feedback loop.
To check for linter errors, run the command npm run lint
from the terminal. The handout contains the same eslint configuration that is used by our grading script.