├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── buildall.sh
├── dependencies
├── package.json
└── yarn.lock
├── docs
├── exams
│ ├── mock_exam.docx
│ └── mock_exam.pdf
├── exercises
│ └── quiz-game
│ │ ├── part-01.md
│ │ ├── part-02.md
│ │ ├── part-03.md
│ │ ├── part-04.md
│ │ ├── part-05.md
│ │ ├── part-06.md
│ │ ├── part-07.md
│ │ ├── part-08.md
│ │ ├── part-09.md
│ │ ├── part-10.md
│ │ ├── part-11.md
│ │ └── part-12.md
├── img
│ └── stephen-leonardi-369733-unsplash-compressed.jpg
└── slides
│ ├── lesson_01.pdf
│ ├── lesson_01.pptx
│ ├── lesson_02.pdf
│ ├── lesson_02.pptx
│ ├── lesson_03.pdf
│ ├── lesson_03.pptx
│ ├── lesson_04.pdf
│ ├── lesson_04.pptx
│ ├── lesson_05.pdf
│ ├── lesson_05.pptx
│ ├── lesson_06.pdf
│ ├── lesson_06.pptx
│ ├── lesson_07.pdf
│ ├── lesson_07.pptx
│ ├── lesson_08.pdf
│ ├── lesson_08.pptx
│ ├── lesson_09.pdf
│ ├── lesson_09.pptx
│ ├── lesson_10.pdf
│ ├── lesson_10.pptx
│ ├── lesson_11.pdf
│ ├── lesson_11.pptx
│ ├── lesson_12.pdf
│ ├── lesson_12.pptx
│ ├── lesson_graphql.pdf
│ └── lesson_graphql.pptx
├── exercise-solutions
└── quiz-game
│ ├── part-01
│ ├── code.js
│ ├── index.html
│ └── style.css
│ ├── part-02
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── index.js
│ │ └── quizzes.js
│ ├── tests
│ │ └── quizzes-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-03
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── index.jsx
│ │ └── quizzes.js
│ ├── tests
│ │ └── quizzes-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-04
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── index.jsx
│ │ ├── match.jsx
│ │ └── quizzes.js
│ ├── tests
│ │ ├── jest-setup.js
│ │ ├── match-test.jsx
│ │ └── quizzes-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-05
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ ├── match.jsx
│ │ │ └── quizzes.js
│ │ └── server
│ │ │ └── server.js
│ ├── tests
│ │ ├── jest-setup.js
│ │ ├── match-test.jsx
│ │ └── quizzes-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-06
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ ├── match.jsx
│ │ │ └── quizzes.js
│ │ └── server
│ │ │ └── server.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-08
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ └── match.jsx
│ │ └── server
│ │ │ ├── app.js
│ │ │ ├── db
│ │ │ └── quizzes.js
│ │ │ ├── routes
│ │ │ └── match-api.js
│ │ │ └── server.js
│ ├── tests
│ │ ├── client
│ │ │ └── match-test.jsx
│ │ ├── jest-setup.js
│ │ ├── mytest-utils.js
│ │ └── server
│ │ │ ├── db
│ │ │ └── quizzes-test.js
│ │ │ └── routes
│ │ │ └── match-api-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-09
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── headerbar.jsx
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ ├── login.jsx
│ │ │ ├── match.jsx
│ │ │ └── signup.jsx
│ │ └── server
│ │ │ ├── app.js
│ │ │ ├── db
│ │ │ ├── quizzes.js
│ │ │ └── users.js
│ │ │ ├── routes
│ │ │ ├── auth-api.js
│ │ │ └── match-api.js
│ │ │ └── server.js
│ ├── tests
│ │ ├── client
│ │ │ └── match-test.jsx
│ │ ├── jest-setup.js
│ │ ├── mytest-utils.js
│ │ └── server
│ │ │ ├── db
│ │ │ └── quizzes-test.js
│ │ │ └── routes
│ │ │ ├── auth-api-test.js
│ │ │ └── match-api-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-10
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── headerbar.jsx
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ ├── login.jsx
│ │ │ ├── match.jsx
│ │ │ └── signup.jsx
│ │ └── server
│ │ │ ├── app.js
│ │ │ ├── db
│ │ │ ├── matches.js
│ │ │ ├── quizzes.js
│ │ │ └── users.js
│ │ │ ├── routes
│ │ │ ├── auth-api.js
│ │ │ └── match-api.js
│ │ │ └── server.js
│ ├── tests
│ │ ├── client
│ │ │ └── match-test.jsx
│ │ ├── jest-setup.js
│ │ ├── mytest-utils.js
│ │ └── server
│ │ │ ├── db
│ │ │ └── quizzes-test.js
│ │ │ └── routes
│ │ │ ├── auth-api-test.js
│ │ │ └── match-api-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── part-11
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── headerbar.jsx
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ ├── login.jsx
│ │ │ ├── match.jsx
│ │ │ └── signup.jsx
│ │ └── server
│ │ │ ├── app.js
│ │ │ ├── db
│ │ │ ├── matches.js
│ │ │ ├── quizzes.js
│ │ │ └── users.js
│ │ │ ├── routes
│ │ │ ├── auth-api.js
│ │ │ └── match-api.js
│ │ │ ├── server.js
│ │ │ └── ws-handler.js
│ ├── tests
│ │ ├── client
│ │ │ └── match-test.jsx
│ │ ├── jest-setup.js
│ │ ├── mytest-utils-ws.js
│ │ ├── mytest-utils.js
│ │ └── server
│ │ │ ├── db
│ │ │ └── quizzes-test.js
│ │ │ └── routes
│ │ │ ├── auth-api-test.js
│ │ │ └── match-api-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ └── part-12
│ ├── .gitignore
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── headerbar.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── login.jsx
│ │ ├── match.jsx
│ │ └── signup.jsx
│ └── server
│ │ ├── app.js
│ │ ├── db
│ │ ├── matches.js
│ │ ├── quizzes.js
│ │ └── users.js
│ │ ├── routes
│ │ ├── auth-api.js
│ │ └── match-api.js
│ │ ├── server.js
│ │ └── ws-handler.js
│ ├── tests
│ ├── client
│ │ ├── headerbar-test.jsx
│ │ ├── home-test.jsx
│ │ ├── login-test.jsx
│ │ ├── match-test.jsx
│ │ └── signup-test.jsx
│ ├── jest-setup.js
│ ├── mytest-utils-ws.js
│ ├── mytest-utils.js
│ └── server
│ │ ├── db
│ │ └── quizzes-test.js
│ │ ├── routes
│ │ ├── auth-api-test.js
│ │ └── match-api-test.js
│ │ └── ws-handler-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── extra-graphql
└── forum
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── news.jsx
│ │ └── user.jsx
│ └── server
│ │ ├── app.js
│ │ ├── db-initializer.js
│ │ ├── db.js
│ │ ├── resolvers.js
│ │ ├── schema.js
│ │ └── server.js
│ ├── tests
│ └── server
│ │ └── app-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les01
├── base-html
│ ├── index.html
│ └── other.html
├── cards
│ ├── code.js
│ ├── img
│ │ ├── 2_of_spades.png
│ │ ├── black_joker.png
│ │ └── cover_card.jpg
│ ├── index.html
│ └── style.css
└── silly
│ └── index.html
├── les02
├── libraries
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── index.js
│ │ └── my-math.js
│ ├── webpack.config.js
│ └── yarn.lock
├── tic-tac-toe
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── board.js
│ │ └── index.js
│ ├── tests
│ │ └── board-test.js
│ ├── webpack.config.js
│ └── yarn.lock
└── unit-tests
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── src
│ ├── index.js
│ └── my-math.js
│ ├── tests
│ ├── index-test.js
│ └── my-math-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les03
├── puzzle15
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── index.jsx
│ │ └── utils.js
│ ├── tests
│ │ └── utils-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── spa-components-js
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── container.js
│ │ ├── counter.js
│ │ ├── index.js
│ │ └── mylib
│ │ │ ├── MyComponent.js
│ │ │ └── MyDOM.js
│ ├── webpack.config.js
│ └── yarn.lock
├── spa-components-react-hooks
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── container.jsx
│ │ ├── counter.jsx
│ │ └── index.jsx
│ ├── webpack.config.js
│ └── yarn.lock
└── spa-components-react
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── container.jsx
│ ├── counter.jsx
│ └── index.jsx
│ ├── webpack.config.js
│ └── yarn.lock
├── les04
├── connect4
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── board.jsx
│ │ ├── connect4.jsx
│ │ ├── index.jsx
│ │ ├── info.jsx
│ │ └── utils.jsx
│ ├── tests
│ │ ├── board-test.jsx
│ │ ├── jest-setup.js
│ │ └── utils-test.js
│ ├── webpack.config.js
│ └── yarn.lock
└── state-lift-up
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── container.jsx
│ ├── counter.jsx
│ └── index.jsx
│ ├── webpack.config.js
│ └── yarn.lock
├── les05
├── games
│ ├── package.json
│ ├── public
│ │ ├── games
│ │ │ ├── cards
│ │ │ │ ├── cards.css
│ │ │ │ └── img
│ │ │ │ │ ├── 2_of_spades.png
│ │ │ │ │ ├── black_joker.png
│ │ │ │ │ └── cover_card.jpg
│ │ │ └── silly
│ │ │ │ └── silly.css
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── games
│ │ │ ├── cards
│ │ │ │ └── cards.jsx
│ │ │ └── silly
│ │ │ │ └── silly.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── my_home_link.jsx
│ │ └── not_found.jsx
│ ├── webpack.config.js
│ └── yarn.lock
├── spa-routing-index-404
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ ├── first.jsx
│ │ │ ├── home.jsx
│ │ │ ├── index.jsx
│ │ │ ├── my_home_link.jsx
│ │ │ ├── not_found.jsx
│ │ │ └── second.jsx
│ │ └── server
│ │ │ └── server.js
│ ├── webpack.config.js
│ └── yarn.lock
├── spa-routing-js
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ ├── mylib.css
│ │ └── style.css
│ ├── src
│ │ └── index.js
│ ├── webpack.config.js
│ └── yarn.lock
├── spa-routing-react
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── first.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── my_home_link.jsx
│ │ ├── not_found.jsx
│ │ └── second.jsx
│ ├── tests
│ │ ├── jest-setup.js
│ │ └── my_home_link-test.js
│ ├── webpack.config.js
│ └── yarn.lock
└── spa-routing-ssr
│ ├── package.json
│ ├── public
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── app.jsx
│ │ ├── first.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── my_home_link.jsx
│ │ ├── not_found.jsx
│ │ └── second.jsx
│ └── server
│ │ ├── renderer.jsx
│ │ └── server.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les06
├── async
│ └── src
│ │ ├── async.js
│ │ └── callback.js
└── weather
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── current.jsx
│ ├── forecast.jsx
│ └── index.jsx
│ ├── tests
│ ├── forecast-test.jsx
│ └── jest-setup.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les08
├── server_client_separated
│ ├── frontend
│ │ ├── package.json
│ │ ├── public
│ │ │ ├── index.html
│ │ │ └── style.css
│ │ ├── src
│ │ │ ├── client
│ │ │ │ ├── book.jsx
│ │ │ │ ├── create.jsx
│ │ │ │ ├── edit.jsx
│ │ │ │ ├── home.jsx
│ │ │ │ ├── index.jsx
│ │ │ │ └── not_found.jsx
│ │ │ └── server
│ │ │ │ └── server.js
│ │ ├── webpack.config.js
│ │ └── yarn.lock
│ └── rest
│ │ ├── package.json
│ │ ├── src
│ │ └── server
│ │ │ ├── app.js
│ │ │ ├── repository.js
│ │ │ └── server.js
│ │ ├── tests
│ │ └── server
│ │ │ └── app-test.js
│ │ └── yarn.lock
└── server_client_together
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── book.jsx
│ │ ├── create.jsx
│ │ ├── edit.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ └── not_found.jsx
│ └── server
│ │ ├── app.js
│ │ ├── repository.js
│ │ └── server.js
│ ├── tests
│ ├── client
│ │ └── home-test.jsx
│ ├── jest-setup.js
│ ├── mytest-utils.js
│ └── server
│ │ └── app-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les09
└── authentication
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── headerbar.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── login.jsx
│ │ └── signup.jsx
│ └── server
│ │ ├── app.js
│ │ ├── repository.js
│ │ ├── routes.js
│ │ └── server.js
│ ├── tests
│ └── server
│ │ └── routes-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les10
├── csrf
│ ├── cat.jpg
│ ├── index-ajax-get.html
│ ├── index-ajax-post-form.html
│ ├── index-ajax-post-json.html
│ ├── index-form-get.html
│ ├── index-form-post.html
│ └── readme.md
├── escape
│ └── escaped.html
└── xss
│ └── react-href
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ └── index.jsx
│ ├── webpack.config.js
│ └── yarn.lock
├── les11
└── chat
│ ├── ajax
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ └── index.jsx
│ │ └── server
│ │ │ ├── app.js
│ │ │ └── server.js
│ ├── webpack.config.js
│ └── yarn.lock
│ ├── server-side
│ ├── package.json
│ ├── public
│ │ └── style.css
│ ├── src
│ │ └── server
│ │ │ ├── app.js
│ │ │ └── server.js
│ └── yarn.lock
│ ├── websocket-full
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── style.css
│ ├── src
│ │ ├── client
│ │ │ └── index.jsx
│ │ └── server
│ │ │ ├── app.js
│ │ │ └── server.js
│ ├── tests
│ │ ├── jest-setup.js
│ │ └── server
│ │ │ └── app-test.js
│ ├── webpack.config.js
│ └── yarn.lock
│ └── websocket-rest
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── home.jsx
│ │ └── index.jsx
│ └── server
│ │ ├── app.js
│ │ └── server.js
│ ├── tests
│ ├── client
│ │ └── home-test.jsx
│ ├── jest-setup.js
│ ├── mytest-utils-ws.js
│ ├── mytest-utils.js
│ └── server
│ │ └── app-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── les12
└── connect4-v2
│ ├── .gitignore
│ ├── package.json
│ ├── public
│ ├── index.html
│ └── style.css
│ ├── src
│ ├── client
│ │ ├── connect4
│ │ │ ├── ai-match.jsx
│ │ │ ├── board.jsx
│ │ │ ├── online-match.jsx
│ │ │ ├── opponent-ai.js
│ │ │ └── opponent-online.js
│ │ ├── headerbar.jsx
│ │ ├── home.jsx
│ │ ├── index.jsx
│ │ ├── login.jsx
│ │ └── signup.jsx
│ ├── server
│ │ ├── app.js
│ │ ├── db
│ │ │ └── users.js
│ │ ├── game
│ │ │ └── match.js
│ │ ├── online
│ │ │ ├── active-players.js
│ │ │ ├── ongoing-matches.js
│ │ │ └── player-queue.js
│ │ ├── routes
│ │ │ ├── auth-api.js
│ │ │ └── match-api.js
│ │ ├── server.js
│ │ └── ws
│ │ │ ├── tokens.js
│ │ │ └── ws-handler.js
│ └── shared
│ │ ├── board-state.js
│ │ └── utils.js
│ ├── tests
│ ├── mytest-utils-ws.js
│ ├── mytest-utils.js
│ └── server
│ │ ├── auth-test.js
│ │ └── match-test.js
│ ├── webpack.config.js
│ └── yarn.lock
├── shared
├── jest-setup.js
├── mytest-utils-ws.js
└── mytest-utils.js
├── syncdep.py
└── template.html
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .idea
3 |
4 | bundle.js
5 |
6 | build
7 |
8 | ###########################################################################################################
9 | # Logs
10 | logs
11 | *.log
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 | *.pid.lock
21 |
22 | # Directory for instrumented libs generated by jscoverage/JSCover
23 | lib-cov
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (https://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 | # Dependency directories
44 | node_modules/
45 | jspm_packages/
46 |
47 | # TypeScript v1 declaration files
48 | typings/
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 |
68 | # next.js build output
69 | .next
70 | /venv/
71 |
--------------------------------------------------------------------------------
/docs/exams/mock_exam.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/exams/mock_exam.docx
--------------------------------------------------------------------------------
/docs/exams/mock_exam.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/exams/mock_exam.pdf
--------------------------------------------------------------------------------
/docs/exercises/quiz-game/part-03.md:
--------------------------------------------------------------------------------
1 | # Quiz Game - Part 03
2 |
3 | In this exercise, the goal is to replace the `index.js` file with a `index.jsx`
4 | using *React* and *React-Dom*.
5 | The JS in such file needs to be modified to use *React*.
6 | Such libraries need to be added as well to the `package.json` file.
7 | You will also likely need to update the `index.html` file.
8 |
9 | Because JSX is not JS, you need to use *Babel* (e.g., with `react` preset)
10 | to transform the JSX code when it is bundled with *WebPack*.
11 | This means adding the proper `module:rules` in `webpack-config.js`.
12 | Then, `libraryTarget` and `library` can be removed, as no longer needed.
13 |
14 | `quizzes.js` should be left as it is, with the test cases still working correctly
15 | with *Jest*.
16 |
17 | Solutions to this exercise can be found under the *exercise-solutions* folder.
18 |
--------------------------------------------------------------------------------
/docs/exercises/quiz-game/part-04.md:
--------------------------------------------------------------------------------
1 | # Quiz Game - Part 04
2 |
3 | Create a new file called `match.jsx`.
4 | Move most of the code from `index.jsx` into `match.jsx`.
5 | The component ` ` will deal with rendering a quiz,
6 | whereas the component ` ` in `index.jsx` simply
7 | uses ` ` and calls `ReactDOM.render()`.
8 |
9 | Create a new `match-test.jsx` test file for the ` ` component.
10 | You need to use *Jest* and *Enzyme*.
11 | To setup *Enzyme* in *Jest*, you can just copy&paste the `jest-setup.js` configuration
12 | file from class, and update the `package.json` accordingly.
13 | Pay particular attention on how to handle the `alert()` function in JS,
14 | which you will need to manually define in the `global` scope.
15 | Write enough tests to achieve at least 80% statement coverage for `match.jsx`.
16 |
17 |
18 | Solutions to this exercise can be found under the *exercise-solutions* folder.
19 |
--------------------------------------------------------------------------------
/docs/exercises/quiz-game/part-07.md:
--------------------------------------------------------------------------------
1 | # Quiz Game - Part 07
2 |
3 | As today's class was just theoretical, there is no exercise.
--------------------------------------------------------------------------------
/docs/exercises/quiz-game/part-11.md:
--------------------------------------------------------------------------------
1 | # Quiz Game - Part 11
2 |
3 | Add a WebSocket to connect to the backend.
4 | You will need to use `express-ws` to add WebSocket support for your Express app.
5 | We will just have a simple functionality of
6 | counting and displaying (e.g., in the homepage) the number of online players.
7 | Every time a player connects (does not need to log in), the counter should go up by 1.
8 | When s/he disconnects, the counter should go down by 1.
9 | For example, during development/testing, if you open the homepage with Chrome, the counter
10 | should show 1. When then you open the same page with Firefox, the counter should be 2 on
11 | both browsers (Chrome would then be automatically updated with WebSocket).
12 | When then you shut down Firefox, the counter in Chrome should go down to 1.
13 |
14 |
15 | Solutions to this exercise can be found under the *exercise-solutions* folder.
--------------------------------------------------------------------------------
/docs/exercises/quiz-game/part-12.md:
--------------------------------------------------------------------------------
1 | # Quiz Game - Part 10
2 |
3 |
4 | Add enough test cases to achieve _at least_ 70% code coverage.
5 | This means that, when you run `yarn test` (which should execute `jest --coverage`),
6 | then the `% Stmts` entry for `All files` should be greater or equal than `70.0`.
7 | What kind of tests to add is up to you.
8 |
9 |
10 | Create an account on Heroku (or any other cloud-provider you prefer), and deploy this
11 | application on it.
12 | In case of Heroku, remember to have a `.gitignored` file, and possibly configure
13 | Heroku settings via the `engines` entry in the `package.json` configuration file.
14 |
15 |
16 | Solutions to this exercise can be found under the *exercise-solutions* folder.
--------------------------------------------------------------------------------
/docs/img/stephen-leonardi-369733-unsplash-compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/img/stephen-leonardi-369733-unsplash-compressed.jpg
--------------------------------------------------------------------------------
/docs/slides/lesson_01.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_01.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_01.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_01.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_02.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_02.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_02.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_02.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_03.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_03.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_03.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_03.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_04.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_04.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_04.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_04.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_05.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_05.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_05.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_05.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_06.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_06.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_06.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_06.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_07.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_07.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_07.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_07.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_08.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_08.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_08.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_08.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_09.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_09.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_09.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_09.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_10.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_10.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_10.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_10.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_11.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_11.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_11.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_11.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_12.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_12.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_12.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_12.pptx
--------------------------------------------------------------------------------
/docs/slides/lesson_graphql.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_graphql.pdf
--------------------------------------------------------------------------------
/docs/slides/lesson_graphql.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_graphql.pptx
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-01/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
10 |
Quiz Game
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-01/style.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-content: center;
5 | align-items: center;
6 | }
7 |
8 | .main-content {
9 | display: flex;
10 | flex-direction: column;
11 | min-width: 50%;
12 | align-items: center;
13 | }
14 |
15 | .heading {
16 | font-size: 4em;
17 | margin: 2rem 0;
18 | }
19 |
20 | .question {
21 | font-size: 1.5em;
22 | font-weight: bold;
23 | }
24 |
25 | .answer {
26 | background-color: #ebe9e4;
27 | margin-bottom: 1rem;
28 | padding: 1rem;
29 | border: 1px solid #787774;
30 | border-radius: 5px;
31 | width: 80%;
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-02/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "presets": [
5 | "@babel/env"
6 | ]
7 | },
8 | "devDependencies": {
9 | "@babel/cli": "7.12.10",
10 | "@babel/core": "7.12.10",
11 | "@babel/preset-env": "7.12.11",
12 | "babel-jest": "24.9.0",
13 | "jest": "24.9.0",
14 | "webpack": "5.11.1",
15 | "webpack-cli": "4.3.1",
16 | "webpack-dev-server": "3.11.1"
17 | },
18 | "engines": {
19 | "node": "^14.0.0"
20 | },
21 | "jest": {
22 | "collectCoverageFrom": [
23 | "src/**/*.(js|jsx)"
24 | ],
25 | "testRegex": "tests/.*-test\\.(js|jsx)$"
26 | },
27 | "license": "LGPL-3.0",
28 | "main": "index.js",
29 | "name": "part-02",
30 | "repository": {},
31 | "scripts": {
32 | "build": "webpack --mode production",
33 | "dev": "webpack serve --open --mode development",
34 | "test": "jest --coverage"
35 | },
36 | "version": "1.0.0"
37 | }
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-02/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
13 |
14 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-02/public/style.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-content: center;
5 | align-items: center;
6 | }
7 |
8 | .main-content {
9 | display: flex;
10 | flex-direction: column;
11 | min-width: 50%;
12 | align-items: center;
13 | }
14 |
15 | .heading {
16 | font-size: 4em;
17 | margin: 2rem 0;
18 | }
19 |
20 | .question {
21 | font-size: 1.5em;
22 | font-weight: bold;
23 | }
24 |
25 | .answer {
26 | background-color: #ebe9e4;
27 | margin-bottom: 1rem;
28 | padding: 1rem;
29 | border: 1px solid #787774;
30 | border-radius: 5px;
31 | width: 80%;
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-02/src/index.js:
--------------------------------------------------------------------------------
1 | import {getRandomQuizzes} from './quizzes';
2 |
3 |
4 | function answerTag(prefix, answer, correct) {
5 |
6 | let onclick;
7 |
8 | if(correct) {
9 | onclick = "alert('Correct!!!'); EntryPoint.displayNewQuiz();";
10 | } else {
11 | onclick = "alert('Wrong answer');";
12 | }
13 |
14 | const html = "" + prefix + answer + "
";
15 |
16 | return html;
17 | }
18 |
19 | function displayQuiz (quiz) {
20 |
21 | let html = "Question: \"" + quiz.question + "\"
";
22 | html += answerTag("A: ", quiz.answers[0], quiz.indexOfRightAnswer === 0);
23 | html += answerTag("B: ", quiz.answers[1], quiz.indexOfRightAnswer === 1);
24 | html += answerTag("C: ", quiz.answers[2], quiz.indexOfRightAnswer === 2);
25 | html += answerTag("D: ", quiz.answers[3], quiz.indexOfRightAnswer === 3);
26 |
27 | const quizDiv = document.getElementById("quizDivId");
28 |
29 | quizDiv.innerHTML = html;
30 | }
31 |
32 | export function displayNewQuiz(){
33 |
34 | const quiz = getRandomQuizzes(1)[0];
35 |
36 | displayQuiz(quiz);
37 | }
38 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-02/tests/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../src/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-02/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/index.js',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public'),
9 | libraryTarget: 'var',
10 | library: 'EntryPoint'
11 | },
12 | devServer: {
13 | contentBase: './public'
14 | },
15 | optimization: {
16 | minimize: true,
17 | minimizer: [new TerserPlugin({
18 | extractComments: false,
19 | })]
20 | }
21 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-03/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-03/public/style.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-content: center;
5 | align-items: center;
6 | }
7 |
8 | .main-content {
9 | display: flex;
10 | flex-direction: column;
11 | min-width: 50%;
12 | align-items: center;
13 | }
14 |
15 | .heading {
16 | font-size: 4em;
17 | margin: 2rem 0;
18 | }
19 |
20 | .question {
21 | font-size: 1.5em;
22 | font-weight: bold;
23 | }
24 |
25 | .answer {
26 | background-color: #ebe9e4;
27 | margin-bottom: 1rem;
28 | padding: 1rem;
29 | border: 1px solid #787774;
30 | border-radius: 5px;
31 | width: 80%;
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-03/tests/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../src/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-03/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/public/style.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-content: center;
5 | align-items: center;
6 | }
7 |
8 | .main-content {
9 | display: flex;
10 | flex-direction: column;
11 | min-width: 50%;
12 | align-items: center;
13 | }
14 |
15 | .heading {
16 | font-size: 4em;
17 | margin: 2rem 0;
18 | }
19 |
20 | .question {
21 | font-size: 1.5em;
22 | font-weight: bold;
23 | }
24 |
25 | .answer {
26 | background-color: #ebe9e4;
27 | margin-bottom: 1rem;
28 | padding: 1rem;
29 | border: 1px solid #787774;
30 | border-radius: 5px;
31 | width: 80%;
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {Match} from "./match";
4 |
5 | const App = () => {
6 | return (
7 |
8 | );
9 | };
10 |
11 | ReactDOM.render( , document.getElementById("root"));
12 |
13 |
14 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/src/match.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {getRandomQuizzes} from './quizzes';
4 |
5 |
6 | export class Match extends React.Component{
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {quiz: getRandomQuizzes(1)[0]}
11 | }
12 |
13 |
14 | handleClick = (correct) => {
15 | if (correct) {
16 | alert('Correct!!!');
17 | this.setState({quiz: getRandomQuizzes(1)[0]})
18 | } else {
19 | alert('Wrong answer');
20 | }
21 | };
22 |
23 |
24 | renderAnswerTag(prefix, answer, correct) {
25 | return this.handleClick(correct)}> {prefix + answer}
;
26 | }
27 |
28 | render() {
29 |
30 | const quiz = this.state.quiz;
31 |
32 | return (
33 | <>
34 | Question: {quiz.question}
35 | {this.renderAnswerTag("A: ", quiz.answers[0], quiz.indexOfRightAnswer === 0)}
36 | {this.renderAnswerTag("B: ", quiz.answers[1], quiz.indexOfRightAnswer === 1)}
37 | {this.renderAnswerTag("C: ", quiz.answers[2], quiz.indexOfRightAnswer === 2)}
38 | {this.renderAnswerTag("D: ", quiz.answers[3], quiz.indexOfRightAnswer === 3)}
39 | >
40 | );
41 | }
42 | }
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/tests/match-test.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const { mount } = require('enzyme');
3 | const {Match} = require("../src/match");
4 |
5 |
6 | function checkQuizIsDisplayed(driver){
7 |
8 | const questions = driver.find('.question');
9 | expect(questions.length).toEqual(1);
10 |
11 | const answers = driver.find('.answer');
12 | expect(answers.length).toEqual(4);
13 | }
14 |
15 | test("Test rendered quiz", ()=> {
16 |
17 | const driver = mount( );
18 | checkQuizIsDisplayed(driver);
19 | });
20 |
21 |
22 | test("Test do answer", () => {
23 |
24 | const driver = mount( );
25 |
26 | let msg = undefined;
27 |
28 | global.alert = (s) => {msg = s};
29 |
30 | const first = driver.find('.answer').at(0);
31 | first.simulate('click');
32 |
33 | checkQuizIsDisplayed(driver);
34 | expect(msg).toBeDefined();
35 | });
36 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/tests/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../src/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-04/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/src/client/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 |
5 | export class Home extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
Quiz Game
14 |
15 |
16 | Welcome to the Quiz Game!
17 | In this game, you will get a series of questions, each one with 4 possible answers.
18 | Only 1 out of the 4 answers is correct.
19 | If you answer wrongly to any of the questions, you lose!
20 | You win if you manage to answer correctly to all questions.
21 |
22 |
23 |
24 |
25 | Play
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom';
4 |
5 | import {Match} from "./match";
6 | import {Home} from "./home";
7 |
8 |
9 | const notFound = () => {
10 | return (
11 |
12 |
NOT FOUND: 404
13 |
14 | ERROR: the page you requested in not available.
15 |
16 |
17 | );
18 | };
19 |
20 |
21 | const App = () => {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 |
36 | ReactDOM.render( , document.getElementById("root"));
37 |
38 |
39 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/src/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const path = require('path');
4 | const app = express();
5 |
6 |
7 | app.use(express.static('public'));
8 |
9 |
10 | app.use((req, res, next) => {
11 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html'));
12 | });
13 |
14 | const port = process.env.PORT || 8080;
15 |
16 | app.listen(port, () => {
17 | console.log('Started server on port ' + port);
18 | });
19 |
20 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/tests/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../src/client/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-05/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/client/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-06/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-06/src/client/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 |
5 | export class Home extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
Quiz Game
14 |
15 |
16 | Welcome to the Quiz Game!
17 | In this game, you will get a series of questions, each one with 4 possible answers.
18 | Only 1 out of the 4 answers is correct.
19 | If you answer wrongly to any of the questions, you lose!
20 | You win if you manage to answer correctly to all questions.
21 |
22 |
23 |
24 |
25 | Play
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-06/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom';
4 |
5 | import {Match} from "./match";
6 | import {Home} from "./home";
7 |
8 |
9 | const notFound = () => {
10 | return (
11 |
12 |
NOT FOUND: 404
13 |
14 | ERROR: the page you requested in not available.
15 |
16 |
17 | );
18 | };
19 |
20 |
21 | const App = () => {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 |
36 | ReactDOM.render( , document.getElementById("root"));
37 |
38 |
39 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-06/src/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const path = require('path');
4 | const app = express();
5 |
6 |
7 | app.use(express.static('public'));
8 |
9 |
10 | app.use((req, res, next) => {
11 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html'));
12 | });
13 |
14 | const port = process.env.PORT || 8080;
15 |
16 | app.listen(port, () => {
17 | console.log('Started server on port ' + port);
18 | });
19 |
20 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-06/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/client/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/src/client/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 |
5 | export class Home extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
Quiz Game
14 |
15 |
16 | Welcome to the Quiz Game!
17 | In this game, you will get a series of questions, each one with 4 possible answers.
18 | Only 1 out of the 4 answers is correct.
19 | If you answer wrongly to any of the questions, you lose!
20 | You win if you manage to answer correctly to all questions.
21 |
22 |
23 |
24 |
25 | Play
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom';
4 |
5 | import {Match} from "./match";
6 | import {Home} from "./home";
7 |
8 |
9 | const notFound = () => {
10 | return (
11 |
12 |
NOT FOUND: 404
13 |
14 | ERROR: the page you requested in not available.
15 |
16 |
17 | );
18 | };
19 |
20 |
21 | const App = () => {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 |
36 | ReactDOM.render( , document.getElementById("root"));
37 |
38 |
39 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/src/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const path = require('path');
4 |
5 | const matchApi = require('./routes/match-api');
6 |
7 | const app = express();
8 |
9 | //to handle JSON payloads
10 | app.use(bodyParser.json());
11 |
12 | //--- Routes -----------
13 | app.use('/api', matchApi);
14 |
15 | app.use(express.static('public'));
16 |
17 | app.use((req, res, next) => {
18 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html'));
19 | });
20 |
21 |
22 | module.exports = app;
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/src/server/routes/match-api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const {getRandomQuizzes} = require('../db/quizzes');
3 |
4 | const router = express.Router();
5 |
6 |
7 |
8 | router.post('/matches', (req, res) => {
9 |
10 | const payload = getRandomQuizzes(3);
11 |
12 | res.status(201).json(payload);
13 | });
14 |
15 |
16 |
17 |
18 | module.exports = router;
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 |
3 | const server = require('http').Server(app);
4 |
5 | const port = process.env.PORT || 8080;
6 |
7 | app.listen(port, () => {
8 | console.log('Started server on port ' + port);
9 | });
10 |
11 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/tests/server/db/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/tests/server/routes/match-api-test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const app = require('../../../src/server/app');
3 |
4 |
5 | test("Test create match", async () =>{
6 |
7 | const response = await request(app).post('/api/matches');
8 |
9 | expect(response.statusCode).toBe(201);
10 | expect(response.body.length).toBe(3);
11 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-08/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/client/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/src/server/db/users.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Here we "simulate" a database with in-memory Map.
4 | Furthermore, we do not deal with the "proper" handling of
5 | passwords. Passwords should NEVER be saved in plain text,
6 | but rather hashed with secure algorithms like BCrypt.
7 | */
8 |
9 | const users = new Map();
10 |
11 |
12 | function getUser(id){
13 |
14 | return users.get(id);
15 | }
16 |
17 | function verifyUser(id, password){
18 |
19 | const user = getUser(id);
20 |
21 | if(!user){
22 | return false;
23 | }
24 |
25 | return user.password === password;
26 | }
27 |
28 | function createUser(id, password){
29 |
30 | if(getUser(id)){
31 | return false;
32 | }
33 |
34 | const user = {
35 | id: id,
36 | password: password,
37 | victories: 0,
38 | defeats: 0
39 | };
40 |
41 | users.set(id, user);
42 | return true;
43 | }
44 |
45 | function resetAllUsers(){
46 | users.clear();
47 | }
48 |
49 | function reportEndOfMatch(userId, isVictory){
50 |
51 | const user = getUser(userId);
52 | if(! user){
53 | throw "Invalid userId: " + userId;
54 | }
55 |
56 | if(isVictory) {
57 | user.victories++;
58 | } else {
59 | user.defeats++;
60 | }
61 | }
62 |
63 |
64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch};
65 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/src/server/routes/match-api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const {getRandomQuizzes} = require('../db/quizzes');
3 |
4 | const router = express.Router();
5 |
6 |
7 |
8 | router.post('/matches', (req, res) => {
9 |
10 | if (!req.user) {
11 | res.status(401).send();
12 | return;
13 | }
14 |
15 | const payload = getRandomQuizzes(3);
16 |
17 | res.status(201).json(payload);
18 | });
19 |
20 |
21 |
22 |
23 | module.exports = router;
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 |
3 | const server = require('http').Server(app);
4 |
5 | const port = process.env.PORT || 8080;
6 |
7 | server.listen(port, () => {
8 | console.log('Started server on port ' + port);
9 | });
10 |
11 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/tests/server/db/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/tests/server/routes/match-api-test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const app = require('../../../src/server/app');
3 |
4 |
5 | test("Test create match no auth", async () =>{
6 |
7 | const response = await request(app).post('/api/matches');
8 |
9 | expect(response.statusCode).toBe(401);
10 | });
11 |
12 |
13 | test("Test create match with auth", async () =>{
14 |
15 | const user = request.agent(app);
16 |
17 | const signup = await user.post('/api/signup')
18 | .send({userId:'match_auth_foo', password:"bar"})
19 | .set('Content-Type', 'application/json');
20 |
21 | expect(signup.statusCode).toBe(201);
22 |
23 | const response = await user.post('/api/matches');
24 |
25 | expect(response.statusCode).toBe(201);
26 | expect(response.body.length).toBe(3);
27 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-09/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/client/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/src/client/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | export class Home extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | componentDidMount() {
10 | if (this.props.user) {
11 | this.props.fetchAndUpdateUserInfo();
12 | }
13 | }
14 |
15 | render() {
16 | const user = this.props.user;
17 |
18 | return (
19 |
20 |
Play the Quiz Game
21 |
22 | Welcome to the Quiz Game! In this game, you will get a series of
23 | questions, each one with 4 possible answers. Only 1 out of the 4
24 | answers is correct. If you answer wrongly to any of the questions, you
25 | lose! You win if you manage to answer correctly to all questions.
26 |
27 |
28 |
29 | {user ? (
30 |
31 |
32 | Play
33 |
34 |
35 |
Victories: {user.victories}
36 |
Defeats: {user.defeats}
37 |
38 |
39 | ) : (
40 |
You need to log-in to start playing!
41 | )}
42 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/src/server/db/matches.js:
--------------------------------------------------------------------------------
1 | const {reportEndOfMatch} = require("./users");
2 | const {getRandomQuizzes} = require("./quizzes");
3 |
4 | /**
5 | * Key -> User Id
6 | * Value -> Match info
7 | */
8 | const matches = new Map();
9 |
10 | let counter = 0;
11 |
12 |
13 | function createMatch(userId, numberOfQuizzes) {
14 |
15 | const ongoing = matches.get(userId);
16 | if (ongoing) {
17 | reportEndOfMatch(userId, false);
18 | }
19 |
20 | const id = counter;
21 | counter++;
22 |
23 | const match = {
24 | id: id,
25 | current: 0,
26 | quizzes: getRandomQuizzes(numberOfQuizzes),
27 | victory: false,
28 | defeat: false
29 | };
30 |
31 | matches.set(userId, match);
32 |
33 | return match;
34 | }
35 |
36 | function getMatch(userId) {
37 | return matches.get(userId);
38 | }
39 |
40 | function removeMatch(userId){
41 | matches.delete(userId);
42 | }
43 |
44 |
45 | module.exports = {getMatch, createMatch, removeMatch};
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/src/server/db/users.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Here we "simulate" a database with in-memory Map.
4 | Furthermore, we do not deal with the "proper" handling of
5 | passwords. Passwords should NEVER be saved in plain text,
6 | but rather hashed with secure algorithms like BCrypt.
7 | */
8 |
9 | const users = new Map();
10 |
11 |
12 | function getUser(id){
13 |
14 | return users.get(id);
15 | }
16 |
17 | function verifyUser(id, password){
18 |
19 | const user = getUser(id);
20 |
21 | if(!user){
22 | return false;
23 | }
24 |
25 | return user.password === password;
26 | }
27 |
28 | function createUser(id, password){
29 |
30 | if(getUser(id)){
31 | return false;
32 | }
33 |
34 | const user = {
35 | id: id,
36 | password: password,
37 | victories: 0,
38 | defeats: 0
39 | };
40 |
41 | users.set(id, user);
42 | return true;
43 | }
44 |
45 | function resetAllUsers(){
46 | users.clear();
47 | }
48 |
49 | function reportEndOfMatch(userId, isVictory){
50 |
51 | const user = getUser(userId);
52 | if(! user){
53 | throw "Invalid userId: " + userId;
54 | }
55 |
56 | if(isVictory) {
57 | user.victories++;
58 | } else {
59 | user.defeats++;
60 | }
61 | }
62 |
63 |
64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch};
65 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/src/server/server.js:
--------------------------------------------------------------------------------
1 | const {app} = require('./app');
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started server on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/tests/server/db/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/tests/server/routes/match-api-test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const {app} = require('../../../src/server/app');
3 |
4 |
5 | test("Test create match no auth", async () =>{
6 |
7 | const response = await request(app).post('/api/matches');
8 |
9 | expect(response.statusCode).toBe(401);
10 | });
11 |
12 |
13 | test("Test create match with auth", async () =>{
14 |
15 | const user = request.agent(app);
16 |
17 | const signup = await user.post('/api/signup')
18 | .send({userId:'match_auth_foo', password:"bar"})
19 | .set('Content-Type', 'application/json');
20 |
21 | expect(signup.statusCode).toBe(201);
22 |
23 | const response = await user.post('/api/matches');
24 |
25 | expect(response.statusCode).toBe(201);
26 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-10/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/client/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/src/server/db/matches.js:
--------------------------------------------------------------------------------
1 | const {reportEndOfMatch} = require("./users");
2 | const {getRandomQuizzes} = require("./quizzes");
3 |
4 | /**
5 | * Key -> User Id
6 | * Value -> Match info
7 | */
8 | const matches = new Map();
9 |
10 | let counter = 0;
11 |
12 |
13 | function createMatch(userId, numberOfQuizzes) {
14 |
15 | const ongoing = matches.get(userId);
16 | if (ongoing) {
17 | reportEndOfMatch(userId, false);
18 | }
19 |
20 | const id = counter;
21 | counter++;
22 |
23 | const match = {
24 | id: id,
25 | current: 0,
26 | quizzes: getRandomQuizzes(numberOfQuizzes),
27 | victory: false,
28 | defeat: false
29 | };
30 |
31 | matches.set(userId, match);
32 |
33 | return match;
34 | }
35 |
36 | function getMatch(userId) {
37 | return matches.get(userId);
38 | }
39 |
40 | function removeMatch(userId){
41 | matches.delete(userId);
42 | }
43 |
44 |
45 | module.exports = {getMatch, createMatch, removeMatch};
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/src/server/db/users.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Here we "simulate" a database with in-memory Map.
4 | Furthermore, we do not deal with the "proper" handling of
5 | passwords. Passwords should NEVER be saved in plain text,
6 | but rather hashed with secure algorithms like BCrypt.
7 | */
8 |
9 | const users = new Map();
10 |
11 |
12 | function getUser(id){
13 |
14 | return users.get(id);
15 | }
16 |
17 | function verifyUser(id, password){
18 |
19 | const user = getUser(id);
20 |
21 | if(!user){
22 | return false;
23 | }
24 |
25 | return user.password === password;
26 | }
27 |
28 | function createUser(id, password){
29 |
30 | if(getUser(id)){
31 | return false;
32 | }
33 |
34 | const user = {
35 | id: id,
36 | password: password,
37 | victories: 0,
38 | defeats: 0
39 | };
40 |
41 | users.set(id, user);
42 | return true;
43 | }
44 |
45 | function resetAllUsers(){
46 | users.clear();
47 | }
48 |
49 | function reportEndOfMatch(userId, isVictory){
50 |
51 | const user = getUser(userId);
52 | if(! user){
53 | throw "Invalid userId: " + userId;
54 | }
55 |
56 | if(isVictory) {
57 | user.victories++;
58 | } else {
59 | user.defeats++;
60 | }
61 | }
62 |
63 |
64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch};
65 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/src/server/server.js:
--------------------------------------------------------------------------------
1 | const {app} = require('./app');
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started server on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/src/server/ws-handler.js:
--------------------------------------------------------------------------------
1 | const express_ws = require('express-ws');
2 |
3 |
4 | let ews;
5 |
6 | function init(app) {
7 |
8 | ews = express_ws(app);
9 |
10 | app.ws('/', function (socket, req) {
11 | console.log('Established a new WS connection');
12 |
13 | broadCastCount();
14 |
15 | //close is treated specially
16 | socket.on('close', () => {
17 | broadCastCount();
18 | });
19 | });
20 | }
21 |
22 | function broadCastCount() {
23 | const n = ews.getWss().clients.size;
24 |
25 | ews.getWss().clients.forEach((client) => {
26 |
27 | const data = JSON.stringify({userCount: n});
28 |
29 | client.send(data);
30 | });
31 | }
32 |
33 |
34 | module.exports = {init};
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/tests/server/db/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/tests/server/routes/match-api-test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const {app} = require('../../../src/server/app');
3 |
4 |
5 | test("Test create match no auth", async () =>{
6 |
7 | const response = await request(app).post('/api/matches');
8 |
9 | expect(response.statusCode).toBe(401);
10 | });
11 |
12 |
13 | test("Test create match with auth", async () =>{
14 |
15 | const user = request.agent(app);
16 |
17 | const signup = await user.post('/api/signup')
18 | .send({userId:'match_auth_foo', password:"bar"})
19 | .set('Content-Type', 'application/json');
20 |
21 | expect(signup.statusCode).toBe(201);
22 |
23 | const response = await user.post('/api/matches');
24 |
25 | expect(response.statusCode).toBe(201);
26 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-11/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/client/index.jsx',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: "babel-loader"
17 | }
18 | }
19 | ]
20 | },
21 | resolve: {
22 | extensions: ['.js', '.jsx']
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/.gitignore:
--------------------------------------------------------------------------------
1 | # Needed for running:
2 | # heroku builds:create -a
3 | #
4 | # Remember you first need to install the "builds" plugin:
5 | # heroku plugins:install heroku-builds
6 |
7 | node_modules
8 | coverage
9 | .idea
10 | .git
11 |
12 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Quiz Game
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/src/server/db/matches.js:
--------------------------------------------------------------------------------
1 | const {reportEndOfMatch} = require("./users");
2 | const {getRandomQuizzes} = require("./quizzes");
3 |
4 | /**
5 | * Key -> User Id
6 | * Value -> Match info
7 | */
8 | const matches = new Map();
9 |
10 | let counter = 0;
11 |
12 |
13 | function createMatch(userId, numberOfQuizzes) {
14 |
15 | const ongoing = matches.get(userId);
16 | if (ongoing) {
17 | reportEndOfMatch(userId, false);
18 | }
19 |
20 | const id = counter;
21 | counter++;
22 |
23 | const match = {
24 | id: id,
25 | current: 0,
26 | quizzes: getRandomQuizzes(numberOfQuizzes),
27 | victory: false,
28 | defeat: false
29 | };
30 |
31 | matches.set(userId, match);
32 |
33 | return match;
34 | }
35 |
36 | function getMatch(userId) {
37 | return matches.get(userId);
38 | }
39 |
40 | function removeMatch(userId){
41 | matches.delete(userId);
42 | }
43 |
44 |
45 | module.exports = {getMatch, createMatch, removeMatch};
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/src/server/db/users.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Here we "simulate" a database with in-memory Map.
4 | Furthermore, we do not deal with the "proper" handling of
5 | passwords. Passwords should NEVER be saved in plain text,
6 | but rather hashed with secure algorithms like BCrypt.
7 | */
8 |
9 | const users = new Map();
10 |
11 |
12 | function getUser(id){
13 |
14 | return users.get(id);
15 | }
16 |
17 | function verifyUser(id, password){
18 |
19 | const user = getUser(id);
20 |
21 | if(!user){
22 | return false;
23 | }
24 |
25 | return user.password === password;
26 | }
27 |
28 | function createUser(id, password){
29 |
30 | if(getUser(id)){
31 | return false;
32 | }
33 |
34 | const user = {
35 | id: id,
36 | password: password,
37 | victories: 0,
38 | defeats: 0
39 | };
40 |
41 | users.set(id, user);
42 | return true;
43 | }
44 |
45 | function resetAllUsers(){
46 | users.clear();
47 | }
48 |
49 | function reportEndOfMatch(userId, isVictory){
50 |
51 | const user = getUser(userId);
52 | if(! user){
53 | throw "Invalid userId: " + userId;
54 | }
55 |
56 | if(isVictory) {
57 | user.victories++;
58 | } else {
59 | user.defeats++;
60 | }
61 | }
62 |
63 |
64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch};
65 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/src/server/server.js:
--------------------------------------------------------------------------------
1 | const {app} = require('./app');
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started server on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/src/server/ws-handler.js:
--------------------------------------------------------------------------------
1 | const express_ws = require('express-ws');
2 |
3 |
4 | let ews;
5 |
6 | function init(app) {
7 |
8 | ews = express_ws(app);
9 |
10 | app.ws('/', function (socket, req) {
11 | console.log('Established a new WS connection');
12 |
13 | broadcastCount();
14 |
15 | //close is treated specially
16 | socket.on('close', () => {
17 | broadcastCount();
18 | });
19 | });
20 | }
21 |
22 | function broadcastCount() {
23 | const n = ews.getWss().clients.size;
24 |
25 | ews.getWss().clients.forEach((client) => {
26 |
27 | const data = JSON.stringify({userCount: n});
28 |
29 | client.send(data);
30 | });
31 | }
32 |
33 |
34 | module.exports = {init};
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/tests/client/home-test.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const {mount} = require('enzyme');
3 | const {MemoryRouter} = require('react-router-dom');
4 |
5 |
6 | const {Home} = require('../../src/client/home');
7 |
8 |
9 | const needToLogInMsg = "You need to log-in to start playing!";
10 |
11 | test("Test not logged in", async () => {
12 |
13 | const driver = mount( );
14 |
15 | const html = driver.html();
16 | expect(html.includes(needToLogInMsg)).toEqual(true);
17 | });
18 |
19 |
20 | test("Test logged in", async () => {
21 |
22 | const victories = 42;
23 | const defeats = 77;
24 |
25 | const user = {id: "Foo", victories, defeats};
26 | const fetchAndUpdateUserInfo = () => new Promise(resolve => resolve());
27 |
28 | const driver = mount(
29 |
30 |
31 |
32 | );
33 |
34 | const html = driver.html();
35 | expect(html.includes(needToLogInMsg)).toEqual(false);
36 | expect(html.includes(victories)).toEqual(true);
37 | expect(html.includes(defeats)).toEqual(true);
38 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/tests/server/db/quizzes-test.js:
--------------------------------------------------------------------------------
1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes");
2 |
3 |
4 | test("Test invalid n", () =>{
5 |
6 | expect(() => getRandomQuizzes(-1)).toThrow();
7 | expect(() => getRandomQuizzes(0)).toThrow();
8 | expect(() => getRandomQuizzes(100000000)).toThrow();
9 | });
10 |
11 |
12 | test("Test get 1", () => {
13 |
14 | const quizzes = getRandomQuizzes(1);
15 |
16 | expect(quizzes.length).toBe(1);
17 | expect(quizzes[0].question).toBeDefined();
18 | expect(quizzes[0].answers).toBeDefined();
19 | expect(quizzes[0].answers.length).toBe(4);
20 | });
21 |
22 | test("Test get 2", () => {
23 |
24 | for(let i=0; i<100; i++) {
25 | const quizzes = getRandomQuizzes(2);
26 |
27 | expect(quizzes.length).toBe(2);
28 | expect(quizzes[0].question).not.toBe(quizzes[1].question);
29 | }
30 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/tests/server/routes/match-api-test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const {app} = require('../../../src/server/app');
3 |
4 |
5 | test("Test create match no auth", async () =>{
6 |
7 | const response = await request(app).post('/api/matches');
8 |
9 | expect(response.statusCode).toBe(401);
10 | });
11 |
12 |
13 | test("Test create match with auth", async () =>{
14 |
15 | const user = request.agent(app);
16 |
17 | const signup = await user.post('/api/signup')
18 | .send({userId:'match_auth_foo', password:"bar"})
19 | .set('Content-Type', 'application/json');
20 |
21 | expect(signup.statusCode).toBe(201);
22 |
23 | const response = await user.post('/api/matches');
24 |
25 | expect(response.statusCode).toBe(201);
26 | });
--------------------------------------------------------------------------------
/exercise-solutions/quiz-game/part-12/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/extra-graphql/forum/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | My News
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/extra-graphql/forum/public/style.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | margin: 0px;
3 | background-color: lightgrey;
4 | padding-left: 20px;
5 | }
6 |
7 | .inputName{
8 | display: inline-block;
9 | margin-left: 10px;
10 | }
11 |
12 | .btn{
13 | border: 2px solid black;
14 | width: 45px;
15 | text-align: center;
16 | cursor: pointer;
17 | }
18 |
--------------------------------------------------------------------------------
/extra-graphql/forum/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom';
4 |
5 | import {Home} from "./home";
6 | import {User} from "./user";
7 | import {News} from "./news";
8 |
9 | const NotFound = () => {
10 |
11 | return (
12 |
13 |
NOT FOUND: 404
14 |
15 | ERROR: the page you requested in not available.
16 |
17 |
18 | );
19 | };
20 |
21 | const App = () => {
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/extra-graphql/forum/src/server/app.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const { ApolloServer, graphiqlExpress} = require('apollo-server-express');
4 |
5 | const resolvers = require('./resolvers');
6 | const typeDefs = require('./schema');
7 |
8 | /*
9 | We still are going to use Express... Apollo will just add to it a
10 | specific endpoint to handle GraphQL requests
11 | */
12 |
13 | const app = express();
14 |
15 | /*
16 | A GraphQL API is defined by:
17 | 1) a schema, specifying what is available
18 | 2) resolvers: to map from schema to our domain model (eg, data in databases)
19 | */
20 | const apollo = new ApolloServer({ typeDefs, resolvers });
21 | apollo.applyMiddleware({ app , path:"/graphql"});
22 |
23 |
24 | //needed to server static files, like HTML, CSS and JS.
25 | app.use(express.static('public'));
26 |
27 |
28 | app.use((req, res, next) => {
29 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html'));
30 | });
31 |
32 | module.exports = app;
--------------------------------------------------------------------------------
/extra-graphql/forum/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 | const {initDB} = require('./db-initializer');
3 |
4 | initDB();
5 |
6 | const port = process.env.PORT || 8080;
7 |
8 | app.listen(port, () => {
9 | console.log('Server started on port ' + port);
10 | });
11 |
12 |
--------------------------------------------------------------------------------
/extra-graphql/forum/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | optimization: {
26 | minimize: true,
27 | minimizer: [new TerserPlugin({
28 | extractComments: false,
29 | })]
30 | }
31 | };
--------------------------------------------------------------------------------
/les01/base-html/other.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Just a Simple Example
6 |
7 |
8 |
9 |
10 | Just Another Page
11 |
12 |
13 |
14 |
15 | Some Lore Ipsum (a placeholder text):
16 |
17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
18 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
19 | laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
20 | voluptate velit esse cillum dolore eu fugiat nulla pariatur.
21 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
22 | deserunt mollit anim id est laborum.
23 |
24 |
25 |
26 |
27 | Return to Home .
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/les01/cards/img/2_of_spades.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les01/cards/img/2_of_spades.png
--------------------------------------------------------------------------------
/les01/cards/img/black_joker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les01/cards/img/black_joker.png
--------------------------------------------------------------------------------
/les01/cards/img/cover_card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les01/cards/img/cover_card.jpg
--------------------------------------------------------------------------------
/les01/cards/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0px;
3 | background-color: rgb(243, 239, 231);
4 | color: rgb(71, 53, 25);
5 | text-align: center;
6 | font-size: 1.2em;
7 | }
8 |
9 |
10 | .card {
11 | margin: 20px;
12 | height: 300px;
13 | border: 2px solid black;
14 | border-radius: 5px;
15 | cursor: pointer;
16 | }
17 |
18 | .selectedCard{
19 | box-shadow: 10px 10px 30px 1px blue;
20 | }
21 |
22 | .outputMessage{
23 | background-color: green;
24 | color: black;
25 | }
26 |
27 | .backcover:hover{
28 | box-shadow: 10px 10px 30px 1px yellow;
29 | }
30 |
31 | .fakeLink{
32 | color: blue;
33 | cursor: pointer;
34 | text-decoration: underline;
35 | }
--------------------------------------------------------------------------------
/les02/libraries/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "dependencies": {
4 | "lodash": "4.17.20"
5 | },
6 | "description": "",
7 | "devDependencies": {
8 | "webpack": "5.11.1",
9 | "webpack-cli": "4.3.1",
10 | "webpack-dev-server": "3.11.1"
11 | },
12 | "engines": {
13 | "node": "^14.0.0"
14 | },
15 | "keywords": [],
16 | "license": "LGPL-3.0",
17 | "name": "basics",
18 | "private": true,
19 | "scripts": {
20 | "build": "webpack --mode production",
21 | "dev": "webpack serve --open --mode development",
22 | "test": "echo \"Error: no test specified\" && exit 1"
23 | },
24 | "version": "1.0.0"
25 | }
--------------------------------------------------------------------------------
/les02/libraries/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | When we "import" the default object exported by a library, we can give it any name
3 | we want, eg., "_" in this case.
4 | Such name is then use as a variable in the rest of this module
5 | */
6 | import _ from 'lodash';
7 |
8 | /*
9 | A library/module could export several different objects/functions.
10 | In such case, a common approach is to "import" an object definition
11 | where each element we want to use from that module is declared.
12 | This creates variables associated to those names, which then can be
13 | used directly.
14 | */
15 | import {computeMin, computeMax, computeMean, computeSum} from "./my-math.js";
16 |
17 |
18 | function initValues() {
19 |
20 | const array = [8,1,5,1,1,2,3];
21 |
22 | const min = computeMin(array);
23 | const max = computeMax(array);
24 | const mean = computeMean(array);
25 | const sum = computeSum(array);
26 |
27 | const arrayString = "[" + _.join(array, ", ") + "]";
28 |
29 | document.getElementById("arrayStringId").innerHTML = arrayString;
30 | document.getElementById("minId").innerHTML = min;
31 | document.getElementById("maxId").innerHTML = max;
32 | document.getElementById("meanId").innerHTML = mean;
33 | document.getElementById("sumId").innerHTML = sum;
34 | };
35 |
36 |
37 | initValues();
38 |
--------------------------------------------------------------------------------
/les02/libraries/src/my-math.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 |
4 | export function computeMin(array){
5 | return _.min(array);
6 | }
7 |
8 | export function computeMax(array) {
9 | return _.max(array);
10 | }
11 |
12 | export function computeMean(array){
13 | return _.mean(array);
14 | }
15 |
16 | export function computeSum(array) {
17 | return _.sum(array);
18 | }
--------------------------------------------------------------------------------
/les02/libraries/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: './src/index.js',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public')
9 | },
10 | devServer: {
11 | contentBase: './public'
12 | },
13 | optimization: {
14 | /*
15 | This is done to avoid creation of unwanted license file for the dependencies
16 | */
17 | minimize: true,
18 | minimizer: [new TerserPlugin({
19 | extractComments: false,
20 | })]
21 | }
22 | };
--------------------------------------------------------------------------------
/les02/tic-tac-toe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "presets": [
5 | "@babel/env"
6 | ]
7 | },
8 | "description": "",
9 | "devDependencies": {
10 | "@babel/cli": "7.12.10",
11 | "@babel/core": "7.12.10",
12 | "@babel/preset-env": "7.12.11",
13 | "babel-jest": "24.9.0",
14 | "jest": "24.9.0",
15 | "webpack": "5.11.1",
16 | "webpack-cli": "4.3.1",
17 | "webpack-dev-server": "3.11.1"
18 | },
19 | "engines": {
20 | "node": "^14.0.0"
21 | },
22 | "jest": {
23 | "collectCoverageFrom": [
24 | "src/**/*.(js|jsx)"
25 | ],
26 | "testRegex": "tests/.*-test\\.(js|jsx)$"
27 | },
28 | "keywords": [],
29 | "license": "LGPL-3.0",
30 | "main": "index.js",
31 | "name": "tic-tac-toe",
32 | "scripts": {
33 | "build": "webpack --mode production",
34 | "dev": "webpack serve --open --mode development",
35 | "test": "jest --coverage"
36 | },
37 | "version": "1.0.0"
38 | }
--------------------------------------------------------------------------------
/les02/tic-tac-toe/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tic-Tac-Toe
7 |
8 |
9 |
10 | Tic-Tac-Toe Game
11 |
12 | Tic-Tac-Toe is a very common example in Computer Science / Software Engineering.
13 | Here, we start with a very simplified version where two users play on the same screen.
14 |
15 |
16 |
17 |
18 |
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/les02/tic-tac-toe/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin: 30px;
3 | }
4 |
5 |
6 | .cell {
7 | float: left;
8 | background: white;
9 | border: 1px solid gray;
10 | font-size: 24px;
11 | font-weight: bold;
12 | text-align: center;
13 | height: 30px;
14 | width: 30px;
15 | margin-right: -1px;
16 | margin-top: -1px;
17 | }
18 |
19 |
20 | .game {
21 | margin-top: 50px;
22 | display: flex;
23 | flex-direction: row;
24 | }
25 |
26 | .game-info {
27 | margin-left: 20px;
28 | }
29 |
30 |
31 | .btn {
32 | background-color: rgb(207, 205, 200);
33 | font-size: 1em;
34 | text-align: center;
35 | vertical-align: middle;
36 | border: 2px solid black;
37 | border-radius: 5px;
38 | margin: 5px;
39 | margin-top: 20px;
40 | padding: 3px;
41 | cursor: pointer;
42 | }
43 |
--------------------------------------------------------------------------------
/les02/tic-tac-toe/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | devServer: {
12 | contentBase: './public'
13 | },
14 | optimization: {
15 | minimize: true,
16 | minimizer: [new TerserPlugin({
17 | extractComments: false,
18 | })]
19 | }
20 | };
--------------------------------------------------------------------------------
/les02/unit-tests/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "presets": [
5 | "@babel/env"
6 | ]
7 | },
8 | "description": "",
9 | "devDependencies": {
10 | "@babel/cli": "7.12.10",
11 | "@babel/core": "7.12.10",
12 | "@babel/preset-env": "7.12.11",
13 | "@webpack-cli/serve": "^1.2.2",
14 | "babel-jest": "24.9.0",
15 | "jest": "24.9.0",
16 | "webpack": "4.41.2",
17 | "webpack-cli": "3.3.10",
18 | "webpack-dev-server": "3.9.0"
19 | },
20 | "engines": {
21 | "node": "^14.0.0"
22 | },
23 | "jest": {
24 | "collectCoverageFrom": [
25 | "src/**/*.(js|jsx)"
26 | ],
27 | "testRegex": "tests/.*-test\\.(js|jsx)$"
28 | },
29 | "keywords": [],
30 | "license": "LGPL-3.0",
31 | "main": "index.js",
32 | "name": "unit-tests",
33 | "scripts": {
34 | "build": "webpack --mode production",
35 | "dev": "webpack-dev-server --open --mode development",
36 | "test": "jest --coverage"
37 | },
38 | "version": "1.0.0"
39 | }
40 |
--------------------------------------------------------------------------------
/les02/unit-tests/src/my-math.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 |
4 | export function computeMin(array){
5 | return _.min(array);
6 | }
7 |
8 | export function computeMax(array) {
9 | return _.max(array);
10 | }
11 |
12 | export function computeMean(array){
13 | return _.mean(array);
14 | }
15 |
16 | export function computeSum(array) {
17 | return _.sum(array);
18 | }
--------------------------------------------------------------------------------
/les02/unit-tests/tests/index-test.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tests are run on NodeJS, which uses "require()" to handle modules and
3 | not "import"
4 | */
5 | const index = require('../src/index.js');
6 |
7 | /*
8 | With Jest included in package.json, and properly configured, then
9 | in these files we can use the method "test" to write unit tests.
10 | It takes 2 inputs:
11 | - the name of the test
12 | - a function that is going to be executed, representing the code
13 | of the test
14 |
15 | We use "expect(result).toBe(expectedValue)"
16 | to write check on whether the "result" we get from testing the code
17 | is the expected one. If it is not, then the test fails.
18 | */
19 |
20 |
21 | test('Not a string', () => {
22 | expect(index.validateInput(1)).toBe(false);
23 | });
24 |
25 | test('Invalid character', () => {
26 | expect(index.validateInput("1 <")).toBe(false);
27 | });
28 |
29 |
30 |
31 | test('Single element', () => {
32 | expect(index.validateInput("1")).toBe(true);
33 | });
34 |
35 | test('Multi-elements', () => {
36 | expect(index.validateInput("1, 2")).toBe(true);
37 | });
38 |
39 | test('Negative value', () => {
40 | expect(index.validateInput("-1")).toBe(true);
41 | });
42 |
43 | test('Complex example', () => {
44 | expect(index.validateInput(" -1,2, -3 , 1 ")).toBe(true);
45 | });
46 |
47 |
--------------------------------------------------------------------------------
/les02/unit-tests/tests/my-math-test.js:
--------------------------------------------------------------------------------
1 | const math = require('../src/my-math.js');
2 |
3 |
4 | const array = [8,1,5,1,1,2,3];
5 |
6 | test('Min', () => {
7 | expect(math.computeMin(array)).toEqual(1);
8 | });
9 |
10 |
11 | test('Max', () => {
12 | expect(math.computeMax(array)).toEqual(8);
13 | });
14 |
15 | test('Mean', () => {
16 | expect(math.computeMean(array)).toEqual(3);
17 | });
18 |
19 | test('Sum', () => {
20 | expect(math.computeSum(array)).toEqual(21);
21 | });
--------------------------------------------------------------------------------
/les02/unit-tests/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | /*
11 | The code from bundle.js will be executed when HTML loads it.
12 | But, by default, we cannot access it. To be able to access it from
13 | other JS in the page, we need to export it as a module.
14 | In this case, the module will be called 'EntryPoint', which is a 'var'.
15 | Note: this is just an example. In general, we will not need to do something
16 | like this, as all the code we will run is from bundle.js itself
17 |
18 | NOTE: this does not work yet in WebPack 5 (regression bug)... so need to use v4.
19 | Anyway, this the only case in the course in which are going to do something like this
20 | */
21 | libraryTarget: 'var',
22 | library: 'EntryPoint'
23 | },
24 | devServer: {
25 | contentBase: './public'
26 | },
27 | optimization: {
28 | minimize: true,
29 | minimizer: [new TerserPlugin({
30 | extractComments: false,
31 | })]
32 | }
33 | };
--------------------------------------------------------------------------------
/les03/puzzle15/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 15 Puzzle Game
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/les03/puzzle15/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | background-color: green;
3 | }
4 |
5 | #root{
6 | margin: 30px;
7 | }
8 |
9 | .cell {
10 | float: left;
11 | background: white;
12 | border: 1px solid gray;
13 | font-size: 24px;
14 | font-weight: bold;
15 | text-align: center;
16 | line-height: 30px;
17 | height: 30px;
18 | width: 30px;
19 | margin-right: -1px;
20 | margin-top: -1px;
21 | }
22 |
23 | .index {
24 | float: left;
25 | font-size: 12px;
26 | text-align: center;
27 | width: 30px;
28 | border: 1px solid transparent;
29 | margin-right: -1px;
30 | margin-top: -1px;
31 | }
32 |
33 |
34 | .cell-row:after {
35 | clear: both;
36 | content: "";
37 | display: table;
38 | }
39 |
40 |
41 | .game {
42 | display: flex;
43 | flex-direction: row;
44 | }
45 |
46 | .game-info {
47 | margin-top: 30px;
48 | margin-left: 20px;
49 | font-weight: bold;
50 | }
51 |
52 |
53 | .btn {
54 | width: 90px;
55 | background-color: rgb(207, 205, 200);
56 | font-size: 1em;
57 | text-align: center;
58 | padding: 5px;
59 | border: 2px solid black;
60 | border-radius: 5px;
61 | margin: 5px;
62 | cursor: pointer;
63 | }
64 |
--------------------------------------------------------------------------------
/les03/puzzle15/tests/utils-test.js:
--------------------------------------------------------------------------------
1 | const utils = require("../src/utils");
2 |
3 |
4 | test("Create matrix", () => {
5 |
6 | const r = 2;
7 | const c = 3;
8 | const v = "foo";
9 |
10 | const m = utils.createMatrix(r, c, v);
11 |
12 | expect(m.length).toEqual(r);
13 |
14 | for(let i=0; i{
26 |
27 | const before = "before";
28 | const after = "after";
29 |
30 | const m = utils.createMatrix(4, 7, before);
31 | const copy = utils.cloneMatrix(m);
32 |
33 | expect(copy[2][3]).toEqual(before);
34 |
35 | copy[2][3] = after;
36 |
37 | expect(copy[2][3]).toEqual(after);
38 |
39 | //if deep-cloning, m must not have changed
40 | expect(m[2][3]).toEqual(before);
41 | });
--------------------------------------------------------------------------------
/les03/puzzle15/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les03/spa-components-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "description": "",
4 | "devDependencies": {
5 | "webpack": "5.11.1",
6 | "webpack-cli": "4.3.1",
7 | "webpack-dev-server": "3.11.1"
8 | },
9 | "engines": {
10 | "node": "^14.0.0"
11 | },
12 | "keywords": [],
13 | "license": "LGPL-3.0",
14 | "main": "index.js",
15 | "name": "spa-components-js",
16 | "scripts": {
17 | "build": "webpack --mode production",
18 | "dev": "webpack serve --open --mode development",
19 | "test": "echo \"Error: no test specified\" && exit 1"
20 | },
21 | "version": "1.0.0"
22 | }
--------------------------------------------------------------------------------
/les03/spa-components-js/public/style.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | height: 20px;
3 | width: 20px;
4 | background-color: gray;
5 | font-size: 1em;
6 | border: 2px solid black;
7 | border-radius: 5px;
8 | margin: 5px;
9 | margin-bottom: 20px;
10 | cursor: pointer;
11 | text-align: center;
12 | display: inline-block;
13 | }
--------------------------------------------------------------------------------
/les03/spa-components-js/src/container.js:
--------------------------------------------------------------------------------
1 | import {MyComponent} from "./mylib/MyComponent";
2 | import {Counter} from "./counter";
3 |
4 |
5 | export class Container extends MyComponent {
6 |
7 | constructor(n) {
8 | super();
9 | this.children = Array(n);
10 |
11 | for (let i = 0; i < n; i++) {
12 | this.children[i] = new Counter(i);
13 | }
14 | }
15 |
16 | render() {
17 |
18 | return (
19 | " " +
20 | "
Counters " +
21 | this.children.map(e => e.render()).join("") +
22 | ""
23 | );
24 | }
25 | }
--------------------------------------------------------------------------------
/les03/spa-components-js/src/counter.js:
--------------------------------------------------------------------------------
1 | import {MyComponent} from "./mylib/MyComponent";
2 |
3 |
4 | export class Counter extends MyComponent {
5 |
6 | constructor(name) {
7 | super();
8 | this.name = name;
9 | this.state = {value: 0};
10 | }
11 |
12 | increase() {
13 | this.setState({value: this.state.value + 1});
14 | }
15 |
16 | decrease() {
17 | this.setState({value: this.state.value - 1});
18 | }
19 |
20 | render() {
21 |
22 | const name = this.name ? this.name : "Default";
23 |
24 | return (
25 | "" +
26 | "
Counter for " + name + ": " + this.state.value + "
" +
27 | "
+
" +
28 | "
-
" +
29 | "
"
30 | );
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/les03/spa-components-js/src/index.js:
--------------------------------------------------------------------------------
1 | import {MyComponent} from "./mylib/MyComponent";
2 | import {render} from "./mylib/MyDOM";
3 | import {Container} from "./container";
4 |
5 | class App extends MyComponent {
6 |
7 | constructor() {
8 | super();
9 |
10 | this.container = new Container(3);
11 | this.children = [this.container];
12 | }
13 |
14 | render() {
15 | return (
16 | "" +
17 | "
Example of Single-Page Application Components with Vanilla JavaScript " +
18 | this.container.render() +
19 | ""
20 | );
21 | }
22 | }
23 |
24 |
25 | render(new App(), document.getElementById("root"));
--------------------------------------------------------------------------------
/les03/spa-components-js/src/mylib/MyComponent.js:
--------------------------------------------------------------------------------
1 | import {update} from "./MyDOM"
2 |
3 | let id_counter = 0;
4 |
5 | export class MyComponent {
6 |
7 | constructor() {
8 | //each component will have a unique id
9 | this.id = "MyComponent_id_" + id_counter;
10 | id_counter++;
11 | }
12 |
13 | setState(state) {
14 |
15 | this.state = state;
16 |
17 | //every time there is a state change, we render the components
18 | update();
19 | }
20 |
21 | doesMatchId(id) {
22 | return this.id === id;
23 | }
24 |
25 | /*
26 | In the generated HTML for the component, we will need to be able to
27 | register event handlers.
28 | But those handlers need to refer to the specific component they belong to.
29 | So, the generated JS in the page somehow needs to find the right component to
30 | use its state.
31 | We will use a global support function registered in MyDOM.js.
32 |
33 | This works, but it has limitations: the method must not have input parameters,
34 | and should be a method belonging to the class of the component.
35 | */
36 | methodHandler(method) {
37 |
38 | const name = method.name;
39 |
40 | return "'MyDOM_callMethodOnComponent(\"" + this.id + "\", \"" + name + "\")'";
41 | }
42 | }
--------------------------------------------------------------------------------
/les03/spa-components-js/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | devServer: {
12 | contentBase: './public'
13 | },
14 | optimization: {
15 | minimize: true,
16 | minimizer: [new TerserPlugin({
17 | extractComments: false,
18 | })]
19 | }
20 | };
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "presets": [
5 | "@babel/react"
6 | ]
7 | },
8 | "dependencies": {
9 | "react": "16.12.0",
10 | "react-dom": "16.12.0"
11 | },
12 | "description": "",
13 | "devDependencies": {
14 | "@babel/core": "7.12.10",
15 | "@babel/preset-react": "7.12.10",
16 | "babel-loader": "8.2.2",
17 | "webpack": "5.11.1",
18 | "webpack-cli": "4.3.1",
19 | "webpack-dev-server": "3.11.1"
20 | },
21 | "engines": {
22 | "node": "^14.0.0"
23 | },
24 | "keywords": [],
25 | "license": "LGPL-3.0",
26 | "main": "index.js",
27 | "name": "spa-components-react-hooks",
28 | "scripts": {
29 | "build": "webpack --mode production",
30 | "dev": "webpack serve --open --mode development",
31 | "test": "echo \"Error: no test specified\" && exit 1"
32 | },
33 | "version": "1.0.0"
34 | }
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Example of Single-Page Application Components with React Hooks
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/public/style.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | height: 20px;
3 | width: 20px;
4 | background-color: gray;
5 | font-size: 1em;
6 | border: 2px solid black;
7 | border-radius: 5px;
8 | margin: 5px;
9 | margin-bottom: 20px;
10 | cursor: pointer;
11 | text-align: center;
12 | display: inline-block;
13 | }
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/src/container.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Counter} from "./counter"
3 |
4 | export class Container extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 |
11 | render() {
12 |
13 | const n = this.props.ncounters ? this.props.ncounters : 1;
14 |
15 | return (
16 |
17 |
Counters
18 | {
19 | Array.from(Array(n))
20 | .map((e,i) => )
21 | }
22 |
23 | );
24 | }
25 | }
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/src/counter.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 |
3 | export function Counter(props) {
4 |
5 | /*
6 | - new state, with default value 0
7 | - useState will return an array with two values
8 | - the first array value will be saved in a variable called 'count'
9 | - the second array value will be saved in a variable called 'setCount'
10 | */
11 | const [count, setCount] = useState(0);
12 |
13 | const name = props.name ? props.name : "Default";
14 |
15 | return (
16 |
17 |
Counter for {name}: {count}
18 |
setCount(count + 1)}>+
19 |
setCount(count - 1)}>-
20 |
21 | );
22 | }
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {Container} from "./container";
4 |
5 | const App = () => {
6 |
7 | return(
8 |
9 |
Example of Single-Page Application Components with React Hooks
10 | {/*
11 | This will render the HTML from the Container component with
12 | 3 Counter components inside it.
13 | We simply call the name of component inside a tag.
14 | */}
15 |
16 |
17 | );
18 | };
19 |
20 | /*
21 | This is where we bind our generated HTML via React to the .html file.
22 | Changes in the state of the React Components will automatically trigger
23 | the re-rendering of the HTML.
24 | */
25 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les03/spa-components-react-hooks/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les03/spa-components-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "plugins": [
5 | "@babel/plugin-proposal-class-properties"
6 | ],
7 | "presets": [
8 | "@babel/react"
9 | ]
10 | },
11 | "dependencies": {
12 | "react": "16.12.0",
13 | "react-dom": "16.12.0"
14 | },
15 | "description": "",
16 | "devDependencies": {
17 | "@babel/core": "7.12.10",
18 | "@babel/plugin-proposal-class-properties": "7.12.1",
19 | "@babel/preset-react": "7.12.10",
20 | "babel-loader": "8.2.2",
21 | "webpack": "5.11.1",
22 | "webpack-cli": "4.3.1",
23 | "webpack-dev-server": "3.11.1"
24 | },
25 | "engines": {
26 | "node": "^14.0.0"
27 | },
28 | "keywords": [],
29 | "license": "LGPL-3.0",
30 | "main": "index.js",
31 | "name": "spa-components-react",
32 | "scripts": {
33 | "build": "webpack --mode production",
34 | "dev": "webpack serve --open --mode development",
35 | "test": "echo \"Error: no test specified\" && exit 1"
36 | },
37 | "version": "1.0.0"
38 | }
--------------------------------------------------------------------------------
/les03/spa-components-react/public/style.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | height: 20px;
3 | width: 20px;
4 | background-color: gray;
5 | font-size: 1em;
6 | border: 2px solid black;
7 | border-radius: 5px;
8 | margin: 5px;
9 | margin-bottom: 20px;
10 | cursor: pointer;
11 | text-align: center;
12 | display: inline-block;
13 | }
--------------------------------------------------------------------------------
/les04/connect4/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Connect Four Game
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/les04/connect4/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | background-color: green;
3 | }
4 |
5 | #root{
6 | margin: 30px;
7 | }
8 |
9 | .cell {
10 | float: left;
11 | background: white;
12 | border: 1px solid gray;
13 | font-size: 24px;
14 | font-weight: bold;
15 | text-align: center;
16 | line-height: 30px;
17 | height: 30px;
18 | width: 30px;
19 | margin-right: -1px;
20 | margin-top: -1px;
21 | }
22 |
23 | .index {
24 | float: left;
25 | font-size: 12px;
26 | text-align: center;
27 | width: 30px;
28 | border: 1px solid transparent;
29 | margin-right: -1px;
30 | margin-top: -1px;
31 | }
32 |
33 |
34 | .cell-row:after {
35 | clear: both;
36 | content: "";
37 | display: table;
38 | }
39 |
40 |
41 | .game {
42 | display: flex;
43 | flex-direction: row;
44 | }
45 |
46 | .game-info {
47 | margin-top: 30px;
48 | margin-left: 20px;
49 | font-weight: bold;
50 | }
51 |
52 |
53 | .btn {
54 | width: 90px;
55 | background-color: rgb(207, 205, 200);
56 | font-size: 1em;
57 | text-align: center;
58 | padding: 5px;
59 | border: 2px solid black;
60 | border-radius: 5px;
61 | margin: 5px;
62 | cursor: pointer;
63 | }
64 |
--------------------------------------------------------------------------------
/les04/connect4/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {Connect4} from "./connect4";
4 |
5 | const App = () => {
6 | return(
7 |
8 |
Connect Four Game
9 |
10 |
11 | );
12 | };
13 |
14 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les04/connect4/src/info.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | /*
4 | The info panel has no state... the state belongs to the parent,
5 | and it is passed here as props.
6 | So no need to have a React.Component class.
7 | Can just have a function that takes as input props.
8 | */
9 | export const Info = (props) =>{
10 |
11 | const res = props.result;
12 |
13 | let msg;
14 | if(res === 0){
15 | const player = props.xIsNext ? "X" : "O";
16 | msg = "Next Player is: " + player;
17 | } else if(res === 1){
18 | msg = "X Won!"
19 | } else if(res === 2){
20 | msg = "O Won!"
21 | } else if(res === 3){
22 | msg = "The Game Ended in a Tie!"
23 | } else {
24 | throw "Invalid result code: " + res;
25 | }
26 |
27 | return(
28 |
29 |
{msg}
30 |
New Match
31 |
32 | );
33 | };
--------------------------------------------------------------------------------
/les04/connect4/src/utils.jsx:
--------------------------------------------------------------------------------
1 |
2 | export function createMatrix(rows, columns, value){
3 | return Array(rows).fill(null).map(() => Array(columns).fill(value));
4 | }
5 |
6 |
7 | export function cloneMatrix(matrix){
8 | return matrix.map(e => e.slice());
9 | }
10 |
--------------------------------------------------------------------------------
/les04/connect4/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/les04/connect4/tests/utils-test.js:
--------------------------------------------------------------------------------
1 | const utils = require("../src/utils");
2 |
3 |
4 | test("Create matrix", () => {
5 |
6 | const r = 2;
7 | const c = 3;
8 | const v = "foo";
9 |
10 | const m = utils.createMatrix(r, c, v);
11 |
12 | expect(m.length).toEqual(r);
13 |
14 | for(let i=0; i{
26 |
27 | const before = "before";
28 | const after = "after";
29 |
30 | const m = utils.createMatrix(4, 7, before);
31 | const copy = utils.cloneMatrix(m);
32 |
33 | expect(copy[2][3]).toEqual(before);
34 |
35 | copy[2][3] = after;
36 |
37 | expect(copy[2][3]).toEqual(after);
38 |
39 | //if deep-cloning, m must not have changed
40 | expect(m[2][3]).toEqual(before);
41 | });
--------------------------------------------------------------------------------
/les04/connect4/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les04/state-lift-up/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "plugins": [
5 | "@babel/plugin-proposal-class-properties"
6 | ],
7 | "presets": [
8 | "@babel/env",
9 | "@babel/react"
10 | ]
11 | },
12 | "dependencies": {
13 | "react": "16.12.0",
14 | "react-dom": "16.12.0"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "7.12.10",
18 | "@babel/plugin-proposal-class-properties": "7.12.1",
19 | "@babel/preset-env": "7.12.11",
20 | "@babel/preset-react": "7.12.10",
21 | "babel-loader": "8.2.2",
22 | "webpack": "5.11.1",
23 | "webpack-cli": "4.3.1",
24 | "webpack-dev-server": "3.11.1"
25 | },
26 | "engines": {
27 | "node": "^14.0.0"
28 | },
29 | "license": "LGPL-3.0",
30 | "main": "index.js",
31 | "name": "state-lift-up",
32 | "scripts": {
33 | "build": "webpack --mode production",
34 | "dev": "webpack serve --open --mode development",
35 | "test": "echo \"Error: no test specified\" && exit 1"
36 | },
37 | "version": "1.0.0"
38 | }
--------------------------------------------------------------------------------
/les04/state-lift-up/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Example of Shared State Lifted Up in Ancestor Components
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/les04/state-lift-up/public/style.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | height: 20px;
3 | width: 20px;
4 | background-color: gray;
5 | font-size: 1em;
6 | border: 2px solid black;
7 | border-radius: 5px;
8 | margin: 5px;
9 | margin-bottom: 20px;
10 | cursor: pointer;
11 | text-align: center;
12 | display: inline-block;
13 | }
--------------------------------------------------------------------------------
/les04/state-lift-up/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {Container} from "./container";
4 |
5 | const App = () => {
6 |
7 | return(
8 |
9 |
Example of Shared State Lifted Up in Ancestor Components
10 |
11 |
12 | );
13 | };
14 |
15 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les04/state-lift-up/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les05/games/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "plugins": [
5 | "@babel/plugin-proposal-class-properties"
6 | ],
7 | "presets": [
8 | "@babel/react"
9 | ]
10 | },
11 | "dependencies": {
12 | "react": "16.12.0",
13 | "react-dom": "16.12.0",
14 | "react-router-dom": "5.2.0"
15 | },
16 | "description": "",
17 | "devDependencies": {
18 | "@babel/core": "7.12.10",
19 | "@babel/plugin-proposal-class-properties": "7.12.1",
20 | "@babel/preset-react": "7.12.10",
21 | "babel-loader": "8.2.2",
22 | "webpack": "5.11.1",
23 | "webpack-cli": "4.3.1",
24 | "webpack-dev-server": "3.11.1"
25 | },
26 | "engines": {
27 | "node": "^14.0.0"
28 | },
29 | "keywords": [],
30 | "license": "LGPL-3.0",
31 | "main": "index.js",
32 | "name": "games",
33 | "scripts": {
34 | "build": "webpack --mode production",
35 | "dev": "webpack serve --open --mode development",
36 | "test": "echo \"Error: no test specified\" && exit 1"
37 | },
38 | "version": "1.0.0"
39 | }
--------------------------------------------------------------------------------
/les05/games/public/games/cards/cards.css:
--------------------------------------------------------------------------------
1 | .cardsCard {
2 | margin: 20px;
3 | height: 300px;
4 | background-color: rgb(207, 205, 200);
5 | font-size: 1em;
6 | border: 2px solid black;
7 | border-radius: 5px;
8 | cursor: pointer;
9 | }
10 |
11 | .cardsSelectedCard{
12 | box-shadow: 10px 10px 30px 1px blue;
13 | }
14 |
15 | .cardsCard:hover:not(.cardsUncovered) {
16 | box-shadow: 10px 10px 30px 1px yellow;
17 | }
18 |
19 | .cardsUncovered{
20 | cursor: default;
21 | }
22 |
23 | .cardsOutputMessage{
24 | background-color: green;
25 | }
26 |
27 | .cardsFakeLink{
28 | color: blue;
29 | cursor: pointer;
30 | text-decoration: underline;
31 | }
--------------------------------------------------------------------------------
/les05/games/public/games/cards/img/2_of_spades.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les05/games/public/games/cards/img/2_of_spades.png
--------------------------------------------------------------------------------
/les05/games/public/games/cards/img/black_joker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les05/games/public/games/cards/img/black_joker.png
--------------------------------------------------------------------------------
/les05/games/public/games/cards/img/cover_card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les05/games/public/games/cards/img/cover_card.jpg
--------------------------------------------------------------------------------
/les05/games/public/games/silly/silly.css:
--------------------------------------------------------------------------------
1 | .sillyBtn{
2 | border: 2px solid rgb(30,30,30);
3 | border-radius: 5px;
4 | background-color: rgb(207, 205, 200);
5 | color: rgb(71, 53, 25);
6 | margin: 5px;
7 | padding: 3px;
8 | display: inline-block;
9 | text-align: center;
10 | cursor: pointer;
11 | }
12 |
--------------------------------------------------------------------------------
/les05/games/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Games
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/les05/games/public/style.css:
--------------------------------------------------------------------------------
1 | @import url("games/cards/cards.css");
2 | @import url("games/silly/silly.css");
3 |
4 |
5 | html, body {
6 | margin: 0px;
7 | background-color: rgb(243, 239, 231);
8 | color: rgb(71, 53, 25);
9 | font-size: 1.2em;
10 | position: relative;
11 | }
12 |
13 | #root{
14 | margin: 30px;
15 | }
--------------------------------------------------------------------------------
/les05/games/src/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from 'react-router-dom'
3 |
4 | //Note the use of "alias" in webpack.config.js to handle "~"
5 | import {MyHomeLink} from "~/my_home_link";
6 |
7 |
8 | export const Home = () => {
9 |
10 | return(
11 |
12 |
Which Game Do You Want to Play?
13 |
14 |
15 | Silly
16 | Cards
17 |
18 |
19 | );
20 | };
--------------------------------------------------------------------------------
/les05/games/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'
4 |
5 | import {Home} from "./home";
6 | import {Silly} from "./games/silly/silly";
7 | import {Cards} from "./games/cards/cards";
8 | import {NotFound} from "./not_found";
9 |
10 |
11 | const App = () => {
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les05/games/src/my_home_link.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 | export const MyHomeLink = () =>{
5 |
6 | return(
7 |
8 | Link back to Home.
9 |
10 | );
11 | };
--------------------------------------------------------------------------------
/les05/games/src/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const NotFound = () =>{
5 |
6 | return(
7 |
8 |
NOT FOUND: 404
9 |
10 | ERROR: the page you requested in not available.
11 |
12 |
13 |
14 | );
15 |
16 | };
--------------------------------------------------------------------------------
/les05/games/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | devServer: {
12 | contentBase: './public'
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.jsx$/,
18 | exclude: /node_modules/,
19 | use: {
20 | loader: "babel-loader"
21 | }
22 | }
23 | ]
24 | },
25 | resolve: {
26 | extensions: ['.js', '.jsx'],
27 | alias: {
28 | ['~']: path.resolve(__dirname + '/src')
29 | }
30 | },
31 | optimization: {
32 | minimize: true,
33 | minimizer: [new TerserPlugin({
34 | extractComments: false,
35 | })]
36 | }
37 |
38 | };
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "presets": [
5 | "@babel/react"
6 | ]
7 | },
8 | "dependencies": {
9 | "react": "16.12.0",
10 | "react-dom": "16.12.0",
11 | "react-router-dom": "5.2.0"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "7.12.10",
15 | "@babel/preset-react": "7.12.10",
16 | "babel-loader": "8.2.2",
17 | "webpack": "5.11.1",
18 | "webpack-cli": "4.3.1",
19 | "webpack-dev-server": "3.11.1"
20 | },
21 | "engines": {
22 | "node": "^14.0.0"
23 | },
24 | "license": "LGPL-3.0",
25 | "main": "index.js",
26 | "name": "spa-routing-index-404",
27 | "scripts": {
28 | "build": "webpack --mode production",
29 | "dev": "yarn build && yarn start",
30 | "start": "node src/server/server.js"
31 | },
32 | "version": "1.0.0"
33 | }
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Router Examples
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin: 30px;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/src/client/first.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const First = () =>{
5 |
6 | return(
7 |
8 |
First Page
9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua.
12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
16 | deserunt mollit anim id est laborum.
17 |
18 |
19 |
20 | );
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'
4 |
5 | import {Home} from "./home";
6 | import {First} from "./first";
7 | import {Second} from "./second";
8 | import {NotFound} from "./not_found";
9 |
10 | const App = () => {
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/src/client/my_home_link.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 | export const MyHomeLink = () =>{
5 |
6 | return(
7 |
8 | Link back to Home.
9 |
10 | );
11 | };
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/src/client/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const NotFound = () =>{
5 |
6 | return(
7 |
8 |
NOT FOUND: 404
9 |
10 | ERROR: the page you requested in not available.
11 |
12 |
13 |
14 | );
15 |
16 | };
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/src/client/second.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const Second = () =>{
5 |
6 | return(
7 |
8 |
Second Page
9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua.
12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
16 | deserunt mollit anim id est laborum.
17 |
18 |
19 |
20 | );
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/les05/spa-routing-index-404/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les05/spa-routing-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "description": "",
4 | "devDependencies": {
5 | "webpack": "5.11.1",
6 | "webpack-cli": "4.3.1",
7 | "webpack-dev-server": "3.11.1"
8 | },
9 | "engines": {
10 | "node": "^14.0.0"
11 | },
12 | "keywords": [],
13 | "license": "LGPL-3.0",
14 | "main": "index.js",
15 | "name": "spa-routing-js",
16 | "scripts": {
17 | "build": "webpack --mode production",
18 | "dev": "webpack serve --open --mode development",
19 | "test": "echo \"Error: no test specified\" && exit 1"
20 | },
21 | "version": "1.0.0"
22 | }
--------------------------------------------------------------------------------
/les05/spa-routing-js/public/mylib.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .spaPage{
4 | display: none
5 | }
6 |
7 | .spaLink{
8 | color: blue;
9 | cursor: pointer;
10 | text-decoration: underline;
11 | }
--------------------------------------------------------------------------------
/les05/spa-routing-js/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin: 30px;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/les05/spa-routing-js/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | devServer: {
12 | contentBase: './public'
13 | },
14 | optimization: {
15 | minimize: true,
16 | minimizer: [new TerserPlugin({
17 | extractComments: false,
18 | })]
19 | }
20 | };
--------------------------------------------------------------------------------
/les05/spa-routing-react/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Router Examples
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/les05/spa-routing-react/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin: 30px;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/les05/spa-routing-react/src/first.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const First = () =>{
5 |
6 | return(
7 |
8 |
First Page
9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua.
12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
16 | deserunt mollit anim id est laborum.
17 |
18 |
19 |
20 | );
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/les05/spa-routing-react/src/my_home_link.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 | export const MyHomeLink = () =>{
5 |
6 | return(
7 |
8 | Link back to Home.
9 |
10 | );
11 | };
--------------------------------------------------------------------------------
/les05/spa-routing-react/src/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const NotFound = () =>{
5 |
6 | return(
7 |
8 |
NOT FOUND: 404
9 |
10 | ERROR: the page you requested in not available.
11 |
12 |
13 |
14 | );
15 |
16 | };
--------------------------------------------------------------------------------
/les05/spa-routing-react/src/second.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const Second = () =>{
5 |
6 | return(
7 |
8 |
Second Page
9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua.
12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
16 | deserunt mollit anim id est laborum.
17 |
18 |
19 |
20 | );
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/les05/spa-routing-react/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/les05/spa-routing-react/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "presets": [
5 | "@babel/env",
6 | "@babel/react"
7 | ]
8 | },
9 | "dependencies": {
10 | "react": "16.12.0",
11 | "react-dom": "16.12.0",
12 | "react-router-dom": "5.2.0"
13 | },
14 | "devDependencies": {
15 | "@babel/cli": "7.12.10",
16 | "@babel/core": "7.12.10",
17 | "@babel/preset-env": "7.12.11",
18 | "@babel/preset-react": "7.12.10",
19 | "babel-loader": "8.2.2",
20 | "webpack": "5.11.1",
21 | "webpack-cli": "4.3.1",
22 | "webpack-dev-server": "3.11.1"
23 | },
24 | "engines": {
25 | "node": "^14.0.0"
26 | },
27 | "license": "LGPL-3.0",
28 | "main": "index.js",
29 | "name": "spa-routing-ssr",
30 | "scripts": {
31 | "build": "yarn build:client && yarn build:server",
32 | "build:client": "webpack --mode production",
33 | "build:server": "babel ./src -d build",
34 | "dev": "yarn build && yarn start",
35 | "start": "node build/server/server.js"
36 | },
37 | "version": "1.0.0"
38 | }
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin: 30px;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/client/app.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Switch, Route} from 'react-router-dom'
3 |
4 | import {Home} from "./home";
5 | import {First} from "./first";
6 | import {Second} from "./second";
7 | import {NotFound} from "./not_found";
8 |
9 |
10 | export const App = () => {
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | };
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/client/first.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const First = () =>{
5 |
6 | return(
7 |
8 |
First Page
9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua.
12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
16 | deserunt mollit anim id est laborum.
17 |
18 |
19 |
20 | );
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter} from 'react-router-dom'
4 |
5 | import {App} from "./app"
6 |
7 |
8 | ReactDOM.render(
9 |
10 |
11 |
12 | , document.getElementById("root"));
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/client/my_home_link.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 | export const MyHomeLink = () =>{
5 |
6 | return(
7 |
8 | Link back to Home.
9 |
10 | );
11 | };
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/client/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const NotFound = () =>{
5 |
6 | return(
7 |
8 |
NOT FOUND: 404
9 |
10 | ERROR: the page you requested in not available.
11 |
12 |
13 |
14 | );
15 |
16 | };
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/client/second.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {MyHomeLink} from "./my_home_link";
3 |
4 | export const Second = () =>{
5 |
6 | return(
7 |
8 |
Second Page
9 |
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua.
12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
16 | deserunt mollit anim id est laborum.
17 |
18 |
19 |
20 | );
21 |
22 | };
23 |
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/server/renderer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {renderToString} from 'react-dom/server'
3 | import {StaticRouter} from 'react-router-dom'
4 | import {App} from "../client/app"
5 |
6 |
7 | export const serverSideRender = (url) => {
8 |
9 | /*
10 | Here must use StaticRouter instead of BrowserRouter, as we
11 | are not in a browser
12 | */
13 | const content = renderToString(
14 |
15 |
16 |
17 | );
18 |
19 | const html = `
20 |
21 |
22 |
23 |
24 |
25 | React Router Examples With Server-Side-Rendering (SSR)
26 |
27 |
28 |
29 | ${content}
30 |
31 |
32 |
33 |
34 |
35 | `;
36 |
37 | return html;
38 | };
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/src/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const path = require('path');
4 | const app = express();
5 |
6 | const Renderer = require('./renderer');
7 |
8 |
9 | app.get("/", (req, res) => {
10 | res.send(Renderer.serverSideRender(req.url));
11 | });
12 |
13 | app.use(express.static('public'));
14 |
15 |
16 | app.use((req, res) => {
17 | res.send(Renderer.serverSideRender(req.url));
18 | });
19 |
20 | const port = process.env.PORT || 8080;
21 |
22 | app.listen(port, () => {
23 | console.log('Started server on port ' + port);
24 | });
25 |
26 |
--------------------------------------------------------------------------------
/les05/spa-routing-ssr/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les06/weather/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Weather App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/les06/weather/public/style.css:
--------------------------------------------------------------------------------
1 | body{
2 | background-color: rgb(230, 242, 255);
3 | }
4 |
5 | #root{
6 | margin: 30px;
7 | }
8 |
9 | #weatherTable{
10 | border-collapse: collapse;
11 | width: 50%;
12 | }
13 | #weatherTable td, #weatherTable th {
14 | border: 1px solid rgb(0, 64, 128);
15 | padding: 6px;
16 | }
17 |
18 | #weatherTable tr:nth-child(even){background-color: rgb(77, 166, 255);}
19 |
20 | #weatherTable tr:hover {background-color: #ddd;}
21 |
22 | #weatherTable th {
23 | padding-top: 12px;
24 | padding-bottom: 12px;
25 | text-align: left;
26 | background-color: rgb(51, 153, 255);
27 | color: white;
28 | }
29 | .mainCities{
30 | font-size: 20px;
31 | text-decoration-color: rgb(0, 64, 128);
32 | }
33 |
34 | .form-control{
35 | width: 44%;
36 | height: 25px;
37 | }
38 |
39 | .submitBtn{
40 | height: 30px;
41 | margin-left: 10px;
42 | font-size: 20px;
43 | cursor: pointer;
44 | }
45 |
46 | .forecastMsg{
47 | margin-top: 15px;
48 | margin-bottom: 15px;
49 | font-size: 20px;
50 | }
51 |
--------------------------------------------------------------------------------
/les06/weather/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/les06/weather/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les08/server_client_separated/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Book App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/les08/server_client_separated/frontend/public/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #e6ccb3;
3 | }
4 |
5 | #root {
6 | margin: 30px;
7 | }
8 |
9 | .btn {
10 | background-color: #cb9767;
11 | margin: 2px;
12 | border: none;
13 | border-radius: 20%;
14 | color: #f8f2ec;
15 | padding: 12px 16px;
16 | font-size: 16px;
17 | cursor: pointer;
18 | }
19 |
20 | .btn:hover {
21 | background-color: #c48a54;
22 | }
23 |
24 | .allBooks {
25 | width: 60%;
26 | margin-top: 20px;
27 | padding: 0px;
28 | }
29 |
30 | .oneBook {
31 | padding: 4px;
32 | }
33 |
34 | .bookInput {
35 | width: 50%;
36 | padding: 5px;
37 | display: block;
38 | margin-bottom: 15px;
39 | }
40 |
41 | .inputTitle {
42 | color: #85582e;
43 | display: inline;
44 | }
--------------------------------------------------------------------------------
/les08/server_client_separated/frontend/src/client/create.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Book from "./book";
3 |
4 | export class Create extends React.Component{
5 |
6 | constructor(props){
7 | super(props);
8 | }
9 |
10 | onOk = async (author, title, year, bookId) => {
11 |
12 | const url = "http://localhost:8081/books";
13 |
14 | //note: here bookId is ignored
15 | const payload = {author, title, year};
16 |
17 | let response;
18 |
19 | try {
20 | response = await fetch(url, {
21 | method: "post",
22 | headers: {
23 | 'Content-Type': 'application/json'
24 | },
25 | body: JSON.stringify(payload)
26 | });
27 | } catch (err) {
28 | return false;
29 | }
30 |
31 | return response.status === 201;
32 | };
33 |
34 |
35 | render(){
36 |
37 | return(
38 |
39 |
Create a New Book
40 |
47 |
48 | );
49 | }
50 | }
--------------------------------------------------------------------------------
/les08/server_client_separated/frontend/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'
4 |
5 | import {Home} from "./home";
6 | import {Create} from "./create"
7 | import {Edit} from "./edit";
8 | import {NotFound} from "./not_found";
9 |
10 | const App = () => {
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les08/server_client_separated/frontend/src/client/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const NotFound = () =>{
4 |
5 | return(
6 |
7 |
NOT FOUND: 404
8 |
9 | ERROR: the page you requested in not available.
10 |
11 |
12 | );
13 |
14 | };
--------------------------------------------------------------------------------
/les08/server_client_separated/frontend/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | libraryTarget: 'var',
11 | library: 'EntryPoint'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.jsx$/,
17 | exclude: /node_modules/,
18 | use: {
19 | loader: "babel-loader"
20 | }
21 | }
22 | ]
23 | },
24 | resolve: {
25 | extensions: ['.js', '.jsx']
26 | },
27 | devServer: {
28 | contentBase: './public'
29 | },
30 | optimization: {
31 | minimize: true,
32 | minimizer: [new TerserPlugin({
33 | extractComments: false,
34 | })]
35 | }
36 | };
--------------------------------------------------------------------------------
/les08/server_client_separated/rest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "dependencies": {
4 | "cors": "2.8.5",
5 | "express": "4.17.1"
6 | },
7 | "description": "",
8 | "devDependencies": {
9 | "jest": "24.9.0",
10 | "supertest": "6.0.1"
11 | },
12 | "engines": {
13 | "node": "^14.0.0"
14 | },
15 | "jest": {
16 | "collectCoverageFrom": [
17 | "src/**/*.(js|jsx)"
18 | ],
19 | "testEnvironment": "node",
20 | "testRegex": "tests/.*-test\\.(js|jsx)$"
21 | },
22 | "keywords": [],
23 | "license": "LGPL-3.0",
24 | "main": "src/server/server.js",
25 | "name": "rest",
26 | "scripts": {
27 | "start": "node src/server/server.js",
28 | "test": "jest --coverage"
29 | },
30 | "version": "1.0.0"
31 | }
--------------------------------------------------------------------------------
/les08/server_client_separated/rest/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 | const repository = require("./repository");
3 |
4 | const port = process.env.PORT || 8081;
5 |
6 |
7 | app.listen(port, () => {
8 | repository.initWithSomeBooks();
9 | console.log('Started RESTful API on port ' + port);
10 | });
11 |
12 |
--------------------------------------------------------------------------------
/les08/server_client_together/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Book App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/les08/server_client_together/public/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #e6ccb3;
3 | }
4 |
5 | #root {
6 | margin: 30px;
7 | }
8 |
9 | .btn {
10 | background-color: #cb9767;
11 | margin: 2px;
12 | border: none;
13 | border-radius: 20%;
14 | color: #f8f2ec;
15 | padding: 12px 16px;
16 | font-size: 16px;
17 | cursor: pointer;
18 | }
19 |
20 | .btn:hover {
21 | background-color: #c48a54;
22 | }
23 |
24 | .allBooks {
25 | width: 60%;
26 | margin-top: 20px;
27 | padding: 0px;
28 | }
29 |
30 | .oneBook {
31 | padding: 4px;
32 | }
33 |
34 | .bookInput {
35 | width: 50%;
36 | padding: 5px;
37 | display: block;
38 | margin-bottom: 15px;
39 | }
40 |
41 | .inputTitle {
42 | color: #85582e;
43 | display: inline;
44 | }
--------------------------------------------------------------------------------
/les08/server_client_together/src/client/create.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Book from "./book";
3 |
4 | export class Create extends React.Component{
5 |
6 | constructor(props){
7 | super(props);
8 | }
9 |
10 | onOk = async (author, title, year, bookId) => {
11 |
12 | const url = "/api/books";
13 |
14 | //note: here bookId is ignored
15 | const payload = {author, title, year};
16 |
17 | let response;
18 |
19 | try {
20 | response = await fetch(url, {
21 | method: "post",
22 | headers: {
23 | 'Content-Type': 'application/json'
24 | },
25 | body: JSON.stringify(payload)
26 | });
27 | } catch (err) {
28 | return false;
29 | }
30 |
31 | return response.status === 201;
32 | };
33 |
34 |
35 | render(){
36 |
37 | return(
38 |
39 |
Create a New Book
40 |
47 |
48 | );
49 | }
50 | }
--------------------------------------------------------------------------------
/les08/server_client_together/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import {BrowserRouter, Switch, Route} from 'react-router-dom';
4 |
5 | import {Home} from "./home";
6 | import {Create} from "./create"
7 | import {Edit} from "./edit";
8 | import {NotFound} from "./not_found";
9 |
10 | const App = () => {
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les08/server_client_together/src/client/not_found.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const NotFound = () =>{
4 |
5 | return(
6 |
7 |
NOT FOUND: 404
8 |
9 | ERROR: the page you requested in not available.
10 |
11 |
12 | );
13 |
14 | };
--------------------------------------------------------------------------------
/les08/server_client_together/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 | const repository = require("./repository");
3 |
4 |
5 |
6 | const port = process.env.PORT || 8080;
7 |
8 | app.listen(port, () => {
9 | repository.initWithSomeBooks();
10 | console.log('Started server on port ' + port);
11 | });
12 |
13 |
--------------------------------------------------------------------------------
/les08/server_client_together/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/les08/server_client_together/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | libraryTarget: 'var',
11 | library: 'EntryPoint'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.jsx$/,
17 | exclude: /node_modules/,
18 | use: {
19 | loader: "babel-loader"
20 | }
21 | }
22 | ]
23 | },
24 | resolve: {
25 | extensions: ['.js', '.jsx']
26 | },
27 | devServer: {
28 | contentBase: './public'
29 | },
30 | optimization: {
31 | minimize: true,
32 | minimizer: [new TerserPlugin({
33 | extractComments: false,
34 | })]
35 | }
36 | };
--------------------------------------------------------------------------------
/les09/authentication/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | SPA-Bank
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/les09/authentication/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started server on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/les09/authentication/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | optimization: {
26 | minimize: true,
27 | minimizer: [new TerserPlugin({
28 | extractComments: false,
29 | })]
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/les10/csrf/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les10/csrf/cat.jpg
--------------------------------------------------------------------------------
/les10/csrf/index-ajax-post-json.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Cute Cat!!!
10 |
11 |
12 |
13 | What a Cute Cat!!!
14 |
15 |
16 |
17 |
18 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/les10/csrf/index-form-post.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Cute Cat!!!
10 |
11 |
12 |
13 | What a Cute Cat!!!
14 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/les10/escape/escaped.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | How to represent the symbols of a tag with attribute without
6 | getting them interpreted as HTML tags?
7 |
8 |
9 |
10 | For example:
11 |
12 |
13 |
14 | Foo
15 |
16 |
17 |
18 | vs.
19 |
20 |
21 |
22 | <a href="foo">Foo</a>
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | However, what to escape depends on the context:
31 |
32 |
33 |
34 | "<p>"
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/les10/xss/react-href/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "babel": {
4 | "plugins": [
5 | "@babel/plugin-proposal-class-properties"
6 | ],
7 | "presets": [
8 | "@babel/react"
9 | ]
10 | },
11 | "dependencies": {
12 | "react": "16.12.0",
13 | "react-dom": "16.12.0"
14 | },
15 | "description": "",
16 | "devDependencies": {
17 | "@babel/core": "7.12.10",
18 | "@babel/plugin-proposal-class-properties": "7.12.1",
19 | "@babel/preset-react": "7.12.10",
20 | "babel-loader": "8.2.2",
21 | "webpack": "5.11.1",
22 | "webpack-cli": "4.3.1",
23 | "webpack-dev-server": "3.11.1"
24 | },
25 | "engines": {
26 | "node": "^14.0.0"
27 | },
28 | "keywords": [],
29 | "license": "LGPL-3.0",
30 | "main": "index.js",
31 | "name": "xss-react-href",
32 | "scripts": {
33 | "build": "webpack --mode production",
34 | "dev": "webpack serve --open --mode development",
35 | "test": "echo \"Error: no test specified\" && exit 1"
36 | },
37 | "version": "1.0.0"
38 | }
--------------------------------------------------------------------------------
/les10/xss/react-href/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | XSS in React
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/les10/xss/react-href/public/style.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | margin: 0px;
3 | background-color: lightgrey;
4 | padding-left: 20px;
5 | }
6 |
7 | .inputLink{
8 | display: inline-block;
9 | margin-left: 10px;
10 | }
11 |
12 | .btn{
13 | border: 2px solid black;
14 | width: 45px;
15 | text-align: center;
16 | cursor: pointer;
17 | }
18 |
--------------------------------------------------------------------------------
/les10/xss/react-href/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devServer: {
26 | contentBase: './public'
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
--------------------------------------------------------------------------------
/les11/chat/ajax/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AJAX-based Chat
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/les11/chat/ajax/public/style.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | margin: 0px;
3 | background-color: lightgrey;
4 | padding-left: 20px;
5 | }
6 |
7 | .inputName{
8 | display: inline-block;
9 | margin-left: 10px;
10 | }
11 |
12 | .btn{
13 | border: 2px solid black;
14 | width: 45px;
15 | text-align: center;
16 | cursor: pointer;
17 | }
18 |
--------------------------------------------------------------------------------
/les11/chat/ajax/src/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const app = express();
4 |
5 | //to handle JSON payloads
6 | app.use(bodyParser.json());
7 |
8 | //needed to server static files, like HTML, CSS and JS.
9 | app.use(express.static('public'));
10 |
11 | let counter = 0;
12 |
13 | const messages = [];
14 |
15 |
16 | app.get('/api/messages', (req, res) => {
17 |
18 | const since = req.query["since"];
19 |
20 | const data = messages;
21 |
22 | if (since !== undefined && since !== null) {
23 | res.json(data.filter(m => m.id > since));
24 | } else {
25 | res.json(data);
26 | }
27 | });
28 |
29 |
30 |
31 | app.post('/api/messages', (req, res) => {
32 |
33 | const dto = req.body;
34 |
35 | const id = counter++;
36 |
37 | messages.push({id:id, author: dto.author, text: dto.text});
38 |
39 | res.status(201); //created
40 | res.send();
41 | });
42 |
43 |
44 | module.exports = app;
--------------------------------------------------------------------------------
/les11/chat/ajax/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started Chat on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/les11/chat/ajax/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | optimization: {
26 | minimize: true,
27 | minimizer: [new TerserPlugin({
28 | extractComments: false,
29 | })]
30 | }
31 | };
--------------------------------------------------------------------------------
/les11/chat/server-side/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Andrea Arcuri",
3 | "dependencies": {
4 | "cors": "2.8.5",
5 | "express": "4.17.1"
6 | },
7 | "description": "",
8 | "engines": {
9 | "node": "^14.0.0"
10 | },
11 | "keywords": [],
12 | "license": "LGPL-3.0",
13 | "main": "index.js",
14 | "name": "server-side",
15 | "scripts": {
16 | "dev": "npm run start",
17 | "start": "node src/server/server.js",
18 | "test": "echo \"Error: no test specified\" && exit 1"
19 | },
20 | "version": "1.0.0"
21 | }
--------------------------------------------------------------------------------
/les11/chat/server-side/public/style.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | margin: 0px;
3 | background-color: lightgrey;
4 | padding-left: 20px;
5 | }
6 |
7 | .inputName{
8 | display: inline-block;
9 | margin-left: 10px;
10 | }
11 |
12 | .btn{
13 | border: 2px solid black;
14 | width: 45px;
15 | text-align: center;
16 | cursor: pointer;
17 | }
18 |
--------------------------------------------------------------------------------
/les11/chat/server-side/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started Chat on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/les11/chat/websocket-full/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | WebSocket-based Chat
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/les11/chat/websocket-full/public/style.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | margin: 0px;
3 | background-color: lightgrey;
4 | padding-left: 20px;
5 | }
6 |
7 | .inputName{
8 | display: inline-block;
9 | margin-left: 10px;
10 | }
11 |
12 | .btn{
13 | border: 2px solid black;
14 | width: 45px;
15 | text-align: center;
16 | cursor: pointer;
17 | }
18 |
--------------------------------------------------------------------------------
/les11/chat/websocket-full/src/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 |
6 | app.listen(port, () => {
7 | console.log('Started Chat on port ' + port);
8 | });
9 |
10 |
--------------------------------------------------------------------------------
/les11/chat/websocket-full/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/les11/chat/websocket-full/tests/server/app-test.js:
--------------------------------------------------------------------------------
1 | const app = require('../../src/server/app');
2 |
3 | test("TODO", async () =>{
4 | //TODO
5 | });
6 |
7 |
--------------------------------------------------------------------------------
/les11/chat/websocket-full/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | optimization: {
26 | minimize: true,
27 | minimizer: [new TerserPlugin({
28 | extractComments: false,
29 | })]
30 | }
31 | };
--------------------------------------------------------------------------------
/les11/chat/websocket-rest/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | WebSocket-based Chat
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/les11/chat/websocket-rest/public/style.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | margin: 0px;
3 | background-color: lightgrey;
4 | padding-left: 20px;
5 | }
6 |
7 | .inputName{
8 | display: inline-block;
9 | margin-left: 10px;
10 | }
11 |
12 | .btn{
13 | border: 2px solid black;
14 | width: 45px;
15 | text-align: center;
16 | cursor: pointer;
17 | }
18 |
--------------------------------------------------------------------------------
/les11/chat/websocket-rest/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import {Home} from './home';
5 |
6 | ReactDOM.render( , document.getElementById("root"));
--------------------------------------------------------------------------------
/les11/chat/websocket-rest/src/server/server.js:
--------------------------------------------------------------------------------
1 | const {app} = require("./app");
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 |
6 | app.listen(port, () => {
7 | console.log('Started Chat on port ' + port);
8 | });
9 |
10 |
--------------------------------------------------------------------------------
/les11/chat/websocket-rest/tests/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/les11/chat/websocket-rest/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public'),
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | optimization: {
26 | minimize: true,
27 | minimizer: [new TerserPlugin({
28 | extractComments: false,
29 | })]
30 | }
31 | };
--------------------------------------------------------------------------------
/les12/connect4-v2/.gitignore:
--------------------------------------------------------------------------------
1 | # Needed for running:
2 | # heroku builds:create -a
3 | #
4 | # Remember you first need to install the "builds" plugin:
5 | # heroku plugins:install heroku-builds
6 |
7 | node_modules
8 | coverage
9 | .idea
10 | .git
11 |
12 |
--------------------------------------------------------------------------------
/les12/connect4-v2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Connect Four Game
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/client/connect4/ai-match.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Board} from "./board";
3 | import {OpponentAI} from "./opponent-ai";
4 |
5 |
6 | export class AiMatch extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 |
11 | this.refToBoard = React.createRef();
12 | this.opponent = new OpponentAI();
13 | }
14 |
15 |
16 | render() {
17 |
18 | return (
19 |
20 |
23 |
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/client/connect4/opponent-ai.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export class OpponentAI{
4 |
5 |
6 | playNext(lastInsertedColumn, board){
7 |
8 | const state = board.getBoardState();
9 |
10 | if(state.isGameFinished()){
11 | //nothing to do
12 | return;
13 | }
14 |
15 | /*
16 | Just simulate a delay for the AI, instead of doing
17 | an insertion immediately.
18 | */
19 | const delay = 1000 * Math.random();
20 | setTimeout(() => this.handleNextMove(board), delay);
21 | }
22 |
23 | /*
24 | WARNING:
25 | what happens if a user start a new game before the callback
26 | of the setTimeout is executed?
27 | We would have to handle such case.
28 | */
29 |
30 | handleNextMove(board){
31 |
32 | /*
33 | For simplicity, the "AI" will just choose at random
34 | among the available columns
35 | */
36 | const state = board.getBoardState();
37 |
38 | const options = state.freeColumns();
39 | const chosen = options[Math.floor(options.length * Math.random())];
40 |
41 | const copy = state.copy();
42 | copy.selectColumn(chosen);
43 |
44 | board.setBoardState(copy);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/client/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | export class Home extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | render() {
10 | const userId = this.props.userId;
11 |
12 | return (
13 |
14 |
15 |
Connect Four Game
16 |
17 |
18 | Welcome to the Connect Four Game! In this game, you alternate with
19 | an opponent in adding coins (X or O) into a 6x7 grid. The goal of
20 | the game is to align 4 of your coins, either horizontally,
21 | vertically or diagonally.
22 |
23 |
24 | You can play either against an AI, or against other players online.
25 | However, to play online, you need to create an account and be logged
26 | in.
27 |
28 |
29 |
30 |
31 |
32 | AI Match
33 |
34 | {userId ? (
35 |
36 | Online Match
37 |
38 | ) : (
39 |
Online Match
40 | )}
41 |
42 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/server/db/users.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Here we "simulate" a database with in-memory Map.
4 | Furthermore, we do not deal with the "proper" handling of
5 | passwords. Passwords should NEVER be saved in plain text,
6 | but rather hashed with secure algorithms like BCrypt.
7 | */
8 |
9 | const users = new Map();
10 |
11 |
12 | function getUser(id){
13 |
14 | return users.get(id);
15 | }
16 |
17 | function verifyUser(id, password){
18 |
19 | const user = getUser(id);
20 |
21 | if(!user){
22 | return false;
23 | }
24 |
25 | return user.password === password;
26 | }
27 |
28 | function createUser(id, password){
29 |
30 | if(getUser(id)){
31 | return false;
32 | }
33 |
34 | const user = {
35 | id: id,
36 | password: password
37 | };
38 |
39 | users.set(id, user);
40 | return true;
41 | }
42 |
43 | function resetAllUsers(){
44 | users.clear();
45 | }
46 |
47 |
48 | module.exports = {getUser, verifyUser, createUser, resetAllUsers};
49 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/server/online/active-players.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | For each active player that is currently online, we need to keep
4 | track of their WS sockets.
5 | */
6 |
7 | const socketToUser = new Map();
8 |
9 | const userToSocket = new Map();
10 |
11 |
12 | function registerSocket(socket, userId){
13 |
14 | socketToUser.set(socket.id, userId);
15 | userToSocket.set(userId, socket);
16 | }
17 |
18 | function removeSocket(socketId){
19 |
20 | const userId = socketToUser.get(socketId);
21 | socketToUser.delete(socketId);
22 | userToSocket.delete(userId);
23 | }
24 |
25 |
26 | function removeUser(userId){
27 |
28 | const socketId = userToSocket.get(userId).id;
29 | userToSocket.delete(userId);
30 | socketToUser.delete(socketId);
31 | }
32 |
33 |
34 | function isActive(userId){
35 | return userToSocket.has(userId);
36 | }
37 |
38 | function getSocket(userId){
39 | return userToSocket.get(userId);
40 | }
41 |
42 | function getUser(socketId){
43 | return socketToUser.get(socketId);
44 | }
45 |
46 | module.exports = {registerSocket, removeSocket, removeUser, isActive, getSocket, getUser};
--------------------------------------------------------------------------------
/les12/connect4-v2/src/server/online/ongoing-matches.js:
--------------------------------------------------------------------------------
1 | const Match = require('../game/match');
2 |
3 |
4 | /*
5 | Map from user id to ongoing match
6 | */
7 | const userIdToMatch = new Map();
8 |
9 | /*
10 | Map from match id to ongoing match
11 | */
12 | const matchIdToMatch = new Map();
13 |
14 |
15 | function startMatch(firstId, secondId){
16 |
17 | const match = new Match(firstId, secondId, deleteMatch);
18 |
19 | console.log("Starting a new match: '"+firstId+"' vs. '"+secondId+"', id = " + match.matchId);
20 |
21 | userIdToMatch.set(firstId, match);
22 | userIdToMatch.set(secondId, match);
23 | matchIdToMatch.set(match.matchId, match);
24 |
25 | match.start();
26 | }
27 |
28 | function deleteMatch(matchId){
29 | const match = matchIdToMatch.get(matchId);
30 | if(!match){
31 | return;
32 | }
33 |
34 | match.playerIds.forEach(id => userIdToMatch.delete(id));
35 | matchIdToMatch.delete(match.matchId);
36 | }
37 |
38 | function forfeit(userId){
39 |
40 | const match = userIdToMatch.get(userId);
41 | if(!match){
42 | return;
43 | }
44 |
45 | match.playerIds.forEach(id => userIdToMatch.delete(id));
46 | matchIdToMatch.delete(match.matchId);
47 |
48 | match.sendForfeit(userId);
49 | }
50 |
51 |
52 | module.exports = {startMatch, forfeit};
--------------------------------------------------------------------------------
/les12/connect4-v2/src/server/online/player-queue.js:
--------------------------------------------------------------------------------
1 | /*
2 | When a user wants to play, it needs an opponent.
3 | So, we need at least 2 players.
4 | A user has to wait until an opponent is available.
5 |
6 | If we always match a user against the first available opponent, then
7 | we would not need a queue, as such queue will at most contain a single user
8 | at any point in time.
9 |
10 | We could be more sophisticated. For example, we could try to match players
11 | with same skills (computed based on number of past victories).
12 | And, so, let some users wait on the queue for some seconds until a better opponent appears,
13 | even if the queue is not empty.
14 | However, to keep it simple, here we match against the first available opponent.
15 | */
16 |
17 | const queue = [];
18 |
19 |
20 | function addUser(id){
21 |
22 | if(queue.includes(id)){
23 | return false;
24 | }
25 |
26 | queue.push(id);
27 | return true;
28 | }
29 |
30 |
31 | function size(){
32 | return queue.length;
33 | }
34 |
35 | function hasUser(id){
36 | return queue.includes(id);
37 | }
38 |
39 | function takeUser(){
40 |
41 | if(queue.length === 0){
42 | return null;
43 | }
44 |
45 | return queue.shift();
46 | }
47 |
48 |
49 | module.exports = {addUser, size, takeUser, hasUser};
50 |
51 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/server/server.js:
--------------------------------------------------------------------------------
1 | const {app} = require("./app");
2 |
3 | const port = process.env.PORT || 8080;
4 |
5 | app.listen(port, () => {
6 | console.log('Started NodeJS server on port ' + port);
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/les12/connect4-v2/src/server/ws/tokens.js:
--------------------------------------------------------------------------------
1 | const crypto = require("crypto");
2 |
3 | /*
4 | Map from random tokens to userId
5 | */
6 | const tokens = new Map();
7 |
8 |
9 | const randomId = () => {
10 | /*
11 | A byte is composed of 8 bits.
12 | 10 bytes are 80 bits.
13 | There are 2^80 possible numbers, which is a huge amount.
14 | We want to send such bits as a string.
15 | We do it in "hex" format.
16 | A hex is representing 16 values, 0-9 and A-F, using 4 bits (2^4=16).
17 | So, we need 2 hex characters per byte.
18 | This means that 10 random bytes can be encoded with a a string of
19 | length 20 containing only 0-9 and A-F symbols.
20 | */
21 |
22 | return crypto.randomBytes(10).toString('hex');
23 | };
24 |
25 |
26 | /*
27 | For a given user, associate a new random token.
28 | */
29 | const createToken = (userId) =>{
30 |
31 | const t = randomId();
32 |
33 | tokens.set(t, userId);
34 |
35 | return t;
36 | };
37 |
38 |
39 | /*
40 | A token can be used only once.
41 | If a user needs to re-authenticate a WebSocket,
42 | a new token needs to be generated.
43 | */
44 | const consumeToken = (t) => {
45 |
46 | const userId = tokens.get(t);
47 |
48 | tokens.delete(t);
49 |
50 | return userId;
51 | };
52 |
53 |
54 | module.exports = {createToken, consumeToken};
--------------------------------------------------------------------------------
/les12/connect4-v2/src/shared/utils.js:
--------------------------------------------------------------------------------
1 |
2 | function createMatrix (rows, columns, value){
3 | return Array(rows).fill(null).map(() => Array(columns).fill(value));
4 | }
5 |
6 |
7 | function cloneMatrix(matrix){
8 | if(!matrix){
9 | return null;
10 | }
11 | return matrix.map(e => e.slice());
12 | }
13 |
14 |
15 | module.exports = {createMatrix, cloneMatrix};
--------------------------------------------------------------------------------
/les12/connect4-v2/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 |
5 | module.exports = {
6 | entry: './src/client/index.jsx',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | }
20 | ]
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx'],
24 | alias: {
25 | ['~']: path.resolve(__dirname + '/src')
26 | }
27 | },
28 | optimization: {
29 | minimize: true,
30 | minimizer: [new TerserPlugin({
31 | extractComments: false,
32 | })]
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/shared/jest-setup.js:
--------------------------------------------------------------------------------
1 | // Origin: shared/jest-setup.js
2 |
3 | const {configure } = require('enzyme');
4 | const jsdom = require('jsdom');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 |
8 | /*
9 | To be able to use Enzyme with Jest, we need this file, with this
10 | exact name.
11 | Here, we setup a virtual HTML page (on the server) in which our components
12 | will be "mounted" with Enzyme.
13 | JSDOM is used for such virtual pages on the server.
14 | Note the overriding of some global variables to point to JSDOM.
15 | */
16 |
17 | export function setUpDomEnvironment(url) {
18 | const { JSDOM } = jsdom;
19 | const dom = new JSDOM('', {url: url});
20 | const { window } = dom;
21 |
22 | global.window = window;
23 | global.document = window.document;
24 | global.navigator = {userAgent: 'node.js'};
25 | copyProps(window, global);
26 |
27 | configure({ adapter: new Adapter() });
28 | }
29 |
30 | function copyProps(src, target) {
31 | const props = Object.getOwnPropertyNames(src)
32 | .filter(prop => typeof target[prop] === 'undefined')
33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop));
34 | Object.defineProperties(target, props);
35 | }
36 |
37 | setUpDomEnvironment('http://localhost:80/');
38 |
39 |
40 |
--------------------------------------------------------------------------------
/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TODO
7 |
8 |
9 |
10 |
11 |
12 |
13 | WARNING: You must have JavaScript activated to use this Web Application
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------