diff --git a/.DS_Store b/.DS_Store index 80e1e1e..a182e67 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/API Documentation.md b/API Documentation.md new file mode 100644 index 0000000..2865ea9 --- /dev/null +++ b/API Documentation.md @@ -0,0 +1,290 @@ +# API Documentation + +The EasyWay's API is a JSON-based API. All requests are made to endpoints beginning with: 'http://localhost:3000/api'. +Requests can be made with http. + + +## 1. getServices + +This API endpoint retrieves the list of services that the end-user can avail. + +*Request type:* **GET** +*Output type:* **JSON Array** + +*Sample request:* http://localhost:3000/api/getServices + +*Sample output:* +[ +  { +   "id": 1, +   "name": "AC Maintanence", +   "description": "Any type of AC maintanence such as filter cleaning, part replacement, etc.", +   "category": "Electronics", +   "image_name": "air_conditioning.jpg", +   "price": 80 +  }, +  { +   "id": 2, +   "name": "Plumbing", +   "description": "Sanitary and household plumbing. No sewage service.", +   "category": "Household", +   "image_name": "plumbing.jpg", +   "price": 100 +  }, +  { +   "id": 3, +   "name": "Saloon", +   "description": "Haricut, massage, nailwork, makeup, etc.", +   "category": "Personal Care", +   "image_name": "saloon.jpg", +   "price": 25 +  } +] + +*Service DB Schema:* + +| Parameter | Type | Details | +| ------------- |:-------:| ----------------------------------------------:| +| id | Integer | Unique identifier for the service. PRIMARY KEY | +| name | String | Name of the service. | +| description | String | Description of the service. | +| category | String | Category of the service. | + + +## 2. createService + +This API endpoint creates a new service as specified by the end-user. This is a POST request. +This is sent with the default "Content-Type" header of "application/x-www-form-urlencoded". + +*Request type:* **POST** +*Input body type:* **JSON Object** +*Output type:* **JSON Object** + +*Sample request:* http://localhost:3000/api/createService + +*Sample input:* +{ +  "name": "Tutoring", +  "description": "Take help in assignments and more.", +  "category": "Home tution", +  "price": 15 +} + +*Sample output:* +{ +  "id": 6, +  "name": "Tutoring", +  "description": "Take help in assignments and more.", +  "category": "Home tution", +  "image_name": "default.jpg", +  "price": 15 +} + + +## 3. register + +This API endpoint registers a new user. +This is sent with the default "Content-Type" header of "application/x-www-form-urlencoded" + +*Request type:* **POST** +*Input body type:* **JSON Object** +*Output type:* **JSON Object** + +*Sample request:* http://localhost:3000/api/register + +*Sample input:* +{ +  "name": "alex", +  "email": "alex@ufl.edu", +  "gender": "F", +  "username": "alex", +  "password": "alex1" +} + +*Sample output:* +{ +  "id": 4, +  "name": "alex", +  "username": "alex", +  "password": "alex1", +  "email": "alex@ufl.edu", +  "gender": "F" +} + +*User DB Schema* + +| Parameter | Type | Details | +| ------------- |:-------:| --------------------------------------------------------------------------:| +| id | Integer | Unique identifier for the user. PRIMARY KEY | +| name | String | Name of the user. Must contain First and
Last name separated by space.| +| username | String | Username of the user. | +| email | String | Email ID of the user. Must be of the form
'xxxxx@xxxxxx' | +| gender | String | Gender of the user. Can be either 'M' or 'F'. | +| password | String | Password for the user. Has to be between 7 to
14 characters in length and must contain
atleast one lower case character, one upper
case character and one number. | + + +## 4. login + +This API endpoint logs in a new user. This request is sent with the default +"Content-Type" header of "application/x-www-form-urlencoded". If the user credentials match with that in the +database, it returns the user details in JSON format else it returns a *404 Not Found* error. + +*Request type:* **POST** +*Input body type:* **JSON Object** +*Output type:* **JSON Object** + +*Sample request:* http://localhost:3000/api/login + +*Sample input:* +{ +  "username": "dummy", +  "password": "dumdum" +} + +*Sample output:* +{ +  "id": 1, +  "name": "Dummy Duck", +  "username": "dummy", +  "password": "dumdum", +  "email": "dummy@ufl.edu", +  "gender": "M" +} + + +## 5. getBookings + +This API endpoint retrieves the list of services booked by a user. +This request is sent with the parameter *userId* which is the *id* field of the user. +Returns a *404 Not Found* error if there are no bookings or if the *userId* is invalid. + +*Request type:* **GET** +*Output type:* **JSON Array** + +*Sample request:* http://localhost:3000/api/getBookings?userId=1 + +*Sample output:* +[ +  { +   "id": 1, +   "user_id": 1, +   "service_id": 1, +   "date": "2022-02-15", +   "start_time": "12:30", +   "end_time": "13:30", +   "is_cancelled": false +  }, +  { +   "id": 2, +   "user_id": 1, +   "service_id": 2, +   "date": "2022-02-15", +   "start_time": "16:30", +   "end_time": "17:30", +   "is_cancelled": false +  } +] + +*Bookings DB Schema* + +| Parameter | Type | Details | +| ------------- |:-------:| ---------------------------------------------------------------------:| +| id | Integer | Unique identifier for the service booking. PRIMARY KEY | +| user_id | Integer | Unique identifier for the user who booked this particular service. | +| service_id | Integer | Unique identifier for the service booked by the user. | +| date | String | Date on which the booking was made by the user. Format: "YYYY-MM-DD". | +| start_time | String | Time at which the service booking commences. Format: "HH:MM". | +| end_time | String | Time at which the service booking ends Format: "HH:MM". | +| is_cancelled | Boolean | Cancellation status of the booking. Can be true or false. | + + +## 6. bookService + +This API endpoint creates a new booking for a service by the end-user. +This request is sent with the default "Content-Type" header of "application/x-www-form-urlencoded". It returns a *500 Internal Server Error* status with the message *Time slot unavailable* if it is booked by another user for the same time slot. + +*Request type:* **POST** +*Input body type:* **JSON Object** +*Output type:* **JSON Object** + +*Sample request:* http://localhost:3000/api/bookService + +*Sample input:* +{ +  "user_id": 1, +  "service_id": 2, +  "date": "2022-02-23", +  "start_time": "11:30", +  "end_time": "12:30" +} + +*Sample output:* +{ +  "id": 4, +  "user_id": 1, +  "service_id": 2, +  "date": "2022-02-23", +  "start_time": "11:30", +  "end_time": "12:30", +  "is_cancelled": false +} + +## 7. cancelBooking + +This API endpoint cancels a particular booking for a service by the end-user. +This request is sent with the default "Content-Type" header of "application/x-www-form-urlencoded". The request either +sets the *is_cancelled* field of the booking to *true* and returns a *200 OK* status along with the message +*Booking is cancelled* or if the booking has already been cancelled it returns a *Booking already cancelled* +message. + +*Request type:* **GET** +*Output type:* **String** + +*Sample request:* http://localhost:3000/api/cancelBooking?id=1 + +*Sample output:* +"Booking is cancelled" + +## 8. getCancelledBookings + +This API endpoint retrieves the list of cancelled services by the end-user. +This matches the *user_id* of the parameter in the request URL and finds Bookings which have *is_cancelled* +field set to *true*. If the *user_id* field cannot be found, it returns a *404 Not Found* error. + +*Request type:* **GET** +*Output type:* **JSON Array** + +*Sample request:* http://localhost:3000/api/getCancelledBookings?userId=1 + +*Sample output:* +[ +  { +   "id": 1, +   "user_id": 1, +   "service_id": 1, +   "date": "2022-02-15", +   "start_time": "12:30", +   "end_time": "13:30", +   "is_cancelled": true +  } +] + + +## 9. getServiceInfo + +This API endpoint returns information about a service when given its *id*. + +*Request type:* **GET** +*Output type:* **JSON Object** + +*Sample request:* http://localhost:3000/api/getServiceInfo?serviceId=1 + +*Sample output:* +{ +  "id": 1, +  "name": "AC Maintanence", +  "description": "Any type of AC maintanence such as filter cleaning, part replacement, etc.", +  "category": "Electronics", +  "image_name": "air_conditioning.jpg", +  "price": 80 +} \ No newline at end of file diff --git a/EasywayAPIs.postman_collection.json b/EasywayAPIs.postman_collection.json new file mode 100644 index 0000000..07c5056 --- /dev/null +++ b/EasywayAPIs.postman_collection.json @@ -0,0 +1,451 @@ +{ + "info": { + "_postman_id": "afc0102d-a4a2-4814-82a2-a9b8cf630a81", + "name": "EasyWayAPIs", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Add user", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"alex\",\n \"email\": \"alex@ufl.edu\",\n \"gender\": \"F\",\n \"username\": \"alex\",\n \"password\": \"alex1\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/user", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "user" + ] + } + }, + "response": [ + { + "name": "Add user", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"alex\",\n \"email\": \"alex@ufl.edu\",\n \"gender\": \"F\",\n \"username\": \"alex\",\n \"password\": \"alex1\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/user", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "user" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Accept", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "http://localhost:4200" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Tue, 22 Feb 2022 00:32:35 GMT" + }, + { + "key": "Content-Length", + "value": "95" + } + ], + "cookie": [], + "body": "{\n \"id\": 4,\n \"name\": \"alex\",\n \"username\": \"alex\",\n \"password\": \"alex1\",\n \"email\": \"alex@ufl.edu\",\n \"gender\": \"F\"\n}" + } + ] + }, + { + "name": "Get user details", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/user/dummy", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "user", + "dummy" + ] + } + }, + "response": [ + { + "name": "Get user details", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/user/dummy", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "user", + "dummy" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Origin", + "value": "http://localhost:4200" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Tue, 22 Feb 2022 00:33:36 GMT" + }, + { + "key": "Content-Length", + "value": "104" + } + ], + "cookie": [], + "body": "{\n \"id\": 1,\n \"name\": \"Dummy Duck\",\n \"username\": \"dummy\",\n \"password\": \"dumdum\",\n \"email\": \"dummy@ufl.edu\",\n \"gender\": \"M\"\n}" + } + ] + }, + { + "name": "Get all services", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/services", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "services" + ] + } + }, + "response": [ + { + "name": "Get all services", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/services", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "services" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Origin", + "value": "http://localhost:4200" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Tue, 22 Feb 2022 00:34:25 GMT" + }, + { + "key": "Content-Length", + "value": "639" + } + ], + "cookie": [], + "body": "[\n {\n \"id\": 1,\n \"name\": \"AC Maintanence\",\n \"description\": \"Any type of AC maintanence such as filter cleaning, part replacement, etc.\",\n \"username\": \"Electronics\"\n },\n {\n \"id\": 2,\n \"name\": \"Plumbing\",\n \"description\": \"Sanitary and household plumbing. No sewage service.\",\n \"username\": \"Household\"\n },\n {\n \"id\": 3,\n \"name\": \"Saloon\",\n \"description\": \"Haricut, massage, nailwork, makeup, etc.\",\n \"username\": \"Personal Care\"\n },\n {\n \"id\": 4,\n \"name\": \"Furniture Repair\",\n \"description\": \"Furniture frame repair, drilling, fitting new furniture, etc.\",\n \"username\": \"Household\"\n },\n {\n \"id\": 5,\n \"name\": \"Exterminator\",\n \"description\": \"Pest control, wildlife evac, alligator emergency, etc.\",\n \"username\": \"Animal/Pet\"\n }\n]" + } + ] + }, + { + "name": "Get bookings from a given user", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/bookings/3", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "bookings", + "3" + ] + } + }, + "response": [ + { + "name": "Get bookings from a given user", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/bookings/2", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "bookings", + "2" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Origin", + "value": "http://localhost:4200" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Tue, 22 Feb 2022 00:36:10 GMT" + }, + { + "key": "Content-Length", + "value": "135" + } + ], + "cookie": [], + "body": "{\n \"id\": 2,\n \"user_id\": 0,\n \"service_id\": 3,\n \"date\": \"2022-02-15T16:30:00Z\",\n \"start_time\": \"2022-02-15T16:30:00Z\",\n \"end_time\": \"2022-02-15T17:30:00Z\"\n}" + } + ] + }, + { + "name": "Book a service", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 5,\n \"user_id\": 3,\n \"service_id\": 4,\n \"date\": \"2022-02-15\",\n \"start_time\": \"12:30\",\n \"end_time\": \"13:30\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/bookService", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "bookService" + ] + } + }, + "response": [ + { + "name": "Book a service", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 2,\n \"user_id\": 1,\n \"service_id\": 3,\n \"date\": \"2022-02-15\",\n \"start_date\": \"11:30\",\n \"end_date\": \"13:30\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/bookService", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "bookService" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Accept", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "http://localhost:4200" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Thu, 24 Feb 2022 05:02:08 GMT" + }, + { + "key": "Content-Length", + "value": "65" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Error 1062: Duplicate entry '2' for key 'bookings.id'\"\n}" + }, + { + "name": "Book a service", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 4,\n \"user_id\": 1,\n \"service_id\": 4,\n \"date\": \"2022-02-15\",\n \"start_time\": \"11:30\",\n \"end_time\": \"13:30\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/bookService", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "bookService" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Accept", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "http://localhost:4200" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Date", + "value": "Thu, 24 Feb 2022 06:01:06 GMT" + }, + { + "key": "Content-Length", + "value": "232" + } + ], + "cookie": [], + "body": "{\n \"id\": 4,\n \"user_id\": 1,\n \"service_id\": 4,\n \"date\": \"2022-02-15\",\n \"start_time\": \"11:30\",\n \"end_time\": \"13:30\",\n \"User\": {\n \"id\": 0,\n \"name\": \"\",\n \"username\": \"\",\n \"password\": \"\",\n \"email\": \"\",\n \"gender\": \"\"\n },\n \"Service\": {\n \"id\": 0,\n \"name\": \"\",\n \"description\": \"\",\n \"username\": \"\"\n }\n}" + } + ] + } + ] +} diff --git a/ProjectDocs/.DS_Store b/ProjectDocs/.DS_Store index 6420488..5b87f4a 100644 Binary files a/ProjectDocs/.DS_Store and b/ProjectDocs/.DS_Store differ diff --git a/client/.browserslistrc b/client/.browserslistrc new file mode 100644 index 0000000..4f9ac26 --- /dev/null +++ b/client/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR diff --git a/client/.editorconfig b/client/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/client/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..6c7049a --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,44 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +/cypress/screenshots +/cypress/videos + +# System files +.DS_Store +Thumbs.db diff --git a/client/.vscode/extensions.json b/client/.vscode/extensions.json new file mode 100644 index 0000000..77b3745 --- /dev/null +++ b/client/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/client/.vscode/launch.json b/client/.vscode/launch.json new file mode 100644 index 0000000..740e35a --- /dev/null +++ b/client/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "pwa-chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/client/.vscode/tasks.json b/client/.vscode/tasks.json new file mode 100644 index 0000000..a298b5b --- /dev/null +++ b/client/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..b9762f3 --- /dev/null +++ b/client/README.md @@ -0,0 +1,27 @@ +# Client + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.1.4. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/client/angular.json b/client/angular.json new file mode 100644 index 0000000..9113b38 --- /dev/null +++ b/client/angular.json @@ -0,0 +1,142 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "client": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/client", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/favicon.png", + "src/assets" + ], + "styles": [ + "src/styles.css", + "./node_modules/bootstrap/dist/css/bootstrap.min.css" + ], + "scripts": [ + "./node_modules/jquery/dist/jquery.min.js", + "./node_modules/bootstrap/dist/js/bootstrap.min.js" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "4mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2mb", + "maximumError": "4mb" + } + ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "client:build:production" + }, + "development": { + "browserTarget": "client:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "client:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + } + }, + "cypress-run": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "client:serve" + }, + "configurations": { + "production": { + "devServerTarget": "client:serve:production" + } + } + }, + "cypress-open": { + "builder": "@cypress/schematic:cypress", + "options": { + "watch": true, + "headless": false + } + }, + "e2e": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "client:serve", + "watch": true, + "headless": false + }, + "configurations": { + "production": { + "devServerTarget": "client:serve:production" + } + } + } + } + } + }, + "defaultProject": "client" +} \ No newline at end of file diff --git a/client/cypress.config.ts b/client/cypress.config.ts new file mode 100644 index 0000000..c85e1fc --- /dev/null +++ b/client/cypress.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + projectId: '1ihb3b', + videosFolder: "cypress/videos", + screenshotsFolder: "cypress/screenshots", + fixturesFolder: "cypress/fixtures", + videoCompression: false, + + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require("./cypress/plugins/index.ts")(on, config); + }, + baseUrl: "http://localhost:4200", + }, + + component: { + devServer: { + framework: "angular", + bundler: "webpack", + }, + specPattern: "**/*.cy.ts", + }, +}); diff --git a/client/cypress/e2e/1. EasyWayNavbar.cy.ts b/client/cypress/e2e/1. EasyWayNavbar.cy.ts new file mode 100644 index 0000000..034da33 --- /dev/null +++ b/client/cypress/e2e/1. EasyWayNavbar.cy.ts @@ -0,0 +1,58 @@ +describe('EasyWay Landing Page Test', () => { + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('has the correct title', () => { + cy.title().should('equal', 'EasyWay'); + }); + + it('displays the correct title', () => { + cy.get('header') + .should('have.id','header') + .find('h3') + .find('a') + .should('have.attr','routerLink','/home') + .should('have.text','EasyWay') + }); + + it('has the nav element', () => { + cy.get('#header') + .find('nav') + .should('have.id','nav') + }); + + it('has the Home, Book a Service, Sign In and Get Started buttons', () => { + cy.get('#nav ul li') + .should('have.length', 4) + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('a') + .should('have.attr','href','index.html') + .should('have.text','Home') + } else if (index === 1) { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/services') + .should('have.attr','routerLinkActive','active') + .should('have.text','Book a Service') + } else if (index === 2) { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/login') + .should('have.attr','routerLinkActive','active') + .should('have.text','Sign In') + } else { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/register') + .should('have.attr','routerLinkActive','active') + .should('have.class','button') + .should('have.text','Get Started') + } + }) + + }) + +}) diff --git a/client/cypress/e2e/10. EasyWayProfilePage.cy.ts b/client/cypress/e2e/10. EasyWayProfilePage.cy.ts new file mode 100644 index 0000000..c6e55c1 --- /dev/null +++ b/client/cypress/e2e/10. EasyWayProfilePage.cy.ts @@ -0,0 +1,130 @@ +describe('EasyWay Profile Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Sign In" button', () => { + cy.get('#nav a[routerLink="/login"]').click(); + }); + + it('goes to the User Login page', () => { + cy.url() + .should('include','/login') + .should('equal','http://localhost:4200/login') + }); + + it('fills the login form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('dummy') + .should('have.value','dummy') + } else { + cy.wrap($el) + .find('input') + .type('dumdum') + .should('have.value','dumdum') + } + }) + }); + + it('clicks the form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + it('clicks on the "Profile" button', () => { + cy.get('#nav a[routerLink="/profile"]').click(); + }); + + it('goes to the User Profile page', () => { + cy.url() + .should('include','/profile') + .should('equal','http://localhost:4200/profile') + }); + + it('displays the correct profile image, profile username and email', () => { + cy.get('.rounded') + .children('.row') + .children() + .first() + .should('have.class','col-md-3 border-right') + .children('div') + .children() + .eq(1) + .should('have.class','font-weight-bold') + .next() + .should('have.class','text-black-50') + .parent() + .find('img') + .should('have.class','rounded-circle mt-5') + .should('have.attr','width','150px') + .should('have.attr','src') + .should('include','depositphotos_179308454-stock-illustration-unknown-person-silhouette-glasses-profile.jpg') + }); + + it('displays the correct page name, user full name, user gender and user city', () => { + cy.get('.rounded') + .children('.row') + .children() + .eq(1) + .should('have.class','col-md-5 border-right') + .children('div') + .should('have.class','p-3 py-5') + .children() + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.class','d-flex justify-content-between align-items-center mb-3') + .find('h4') + .should('have.class','text-right') + .should('contain.text','Profile Settings') + } else if(index === 1) { + cy.wrap($el) + .should('have.class','row mt-2') + .find('label') + .should('have.class','labels') + .should('contain.text','Name') + .next() + .should('have.class','form-control') + .should('have.attr','placeholder','Full name') + .should('have.attr', 'readonly', 'readonly') + } else if (index === 2) { + cy.wrap($el) + .should('have.class','row mt-3') + .find('label') + .should('have.class','labels') + .should('contain.text','Gender') + .next() + .should('have.class','form-control') + .should('have.attr','placeholder','Gender') + .should('have.attr', 'readonly', 'readonly') + } else { + cy.wrap($el) + .should('have.class','row mt-3') + .find('label') + .should('have.class','labels') + .should('contain.text','City') + .next() + .should('have.class','form-control') + .should('have.attr','placeholder','City') + .should('have.attr', 'readonly', 'readonly') + } + }) + }); + +}); diff --git a/client/cypress/e2e/11. EasyWayFooter.cy.ts b/client/cypress/e2e/11. EasyWayFooter.cy.ts new file mode 100644 index 0000000..80a92d1 --- /dev/null +++ b/client/cypress/e2e/11. EasyWayFooter.cy.ts @@ -0,0 +1,116 @@ +describe('EasyWay Footer Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('has the footer element', () => { + cy.get('.footer-dark') + .find('footer') + }); + + it('has the About, Services, EasyWay and Social media sections with relevant links', () => { + cy.get('footer') + .children('.container') + .children('.row') + .children() + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.class','col-sm-6 col-md-3 item') + .find('h3') + .should('have.text','Services') + .next() + .children() + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('a') + .should('have.attr','href','#') + .should('contain.text','Home') + } else if (index === 1) { + cy.wrap($el) + .find('a') + .should('have.attr','href','#') + .should('contain.text','Terms') + } else if (index === 2) { + cy.wrap($el) + .find('a') + .should('have.attr','href','#') + .should('contain.text','Privacy Policy') + } else { + cy.wrap($el) + .find('a') + .should('have.attr','href','#') + .should('contain.text','Returns & Refunds') + } + }) + } else if (index === 1) { + cy.wrap($el) + .should('have.class','col-sm-6 col-md-3 item') + .find('h3') + .should('have.text','About') + .next() + .children() + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('a') + .should('have.attr','href','https://github.com/ksharma67/EasyWay') + .should('contain.text','Project') + } else if (index === 2) { + cy.wrap($el) + .find('a') + .should('have.attr','href','#') + .should('contain.text','Careers') + } + }) + } else if (index === 2) { + cy.wrap($el) + .should('have.class','col-md-6 item text') + .find('h3') + .should('have.text','EasyWay') + .parent() + .find('p') + .should('contain.text','The platform helps customers book realiable & high quality services for your home on demand. The services are delivered by highly trained professionals at your time and schedule.') + } else { + cy.wrap($el) + .should('have.class','col item social') + .children() + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.attr','href','#') + .find('i') + .should('have.class','bi bi-facebook fb-icon') + } else if (index === 1) { + cy.wrap($el) + .should('have.attr','href','#') + .find('i') + .should('have.class','bi bi-twitter') + } else if (index === 2) { + cy.wrap($el) + .should('have.attr','href','#') + .find('i') + .should('have.class','bi bi-snapchat') + } else { + cy.wrap($el) + .should('have.attr','href','#') + .find('i') + .should('have.class','bi bi-instagram') + } + }) + } + }) + }) + + it('has the Copyright statement', () => { + cy.get('footer') + .children('.container') + .children() + .eq(1) + .should('have.class','copyright') + .should('contain.text','Copyright EasyWay @ Fall 2022') + }); + +}) diff --git a/client/cypress/e2e/2. EasyWayLandingPage.cy.ts b/client/cypress/e2e/2. EasyWayLandingPage.cy.ts new file mode 100644 index 0000000..41a849b --- /dev/null +++ b/client/cypress/e2e/2. EasyWayLandingPage.cy.ts @@ -0,0 +1,98 @@ +describe('EasyWay Landing Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('displays the correct application name', () => { + cy.get('#banner h2').should('contain.text','EasyWay'); + }); + + it('has the Banner section', () => { + cy.get('section') + .should('have.id','banner') + }); + + it('has the Sign Up button', () => { + cy.get('#banner ul li') + .find('a') + .should('have.attr','href','/register') + .should('have.class','button primary') + .should('contain.text','Sign Up'); + }); + + it('has the Checkout Services button', () => { + cy.get('#banner ul li') + .next() + .find('a') + .should('have.attr','href','/services') + .should('have.class','button') + .should('contain.text','Checkout Services'); + }); + + it('has the Main section', () => { + cy.get('#banner').next() + .should('have.id','main') + .should('have.class','container landing-main') + .find('section') + .should('have.class','box special') + .find('header') + .should('have.class','major') + .next() + .should('have.class', 'image featured') + .find('img') + .should('exist') + }); + + it('has Features section with four features and proper feature titles', () => { + cy.get('#main section') + .next() + .should('have.class', 'box special features') + .find('section') + .should('have.length', 4) + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('span') + .should('have.class','icon solid major accent2') + .find('i') + .should('have.class','bi bi-stars') + .parent() + .next() + .should('have.text','Quality Assurance') + } else if (index === 1) { + cy.wrap($el) + .find('span') + .should('have.class','icon solid major accent3') + .find('i') + .should('have.class','bi bi-wallet2') + .parent() + .next() + .should('have.text','Affordable Prices') + } else if (index === 2) { + cy.wrap($el) + .find('span') + .should('have.class','icon solid major accent4') + .find('i') + .should('have.class','bi bi-person-check') + .parent() + .next() + .should('have.text','Trained Professionals') + } else { + cy.wrap($el) + .find('span') + .should('have.class','icon solid major accent5') + .find('i') + .should('have.class','bi bi-calendar-heart') + .parent() + .next() + .should('have.text','Schedule Friendly') + } + }) + + + }); + + + + }); diff --git a/client/cypress/e2e/3. EasyWayNavbarLoggedOut.cy.ts b/client/cypress/e2e/3. EasyWayNavbarLoggedOut.cy.ts new file mode 100644 index 0000000..a058bff --- /dev/null +++ b/client/cypress/e2e/3. EasyWayNavbarLoggedOut.cy.ts @@ -0,0 +1,58 @@ +describe('EasyWay Logged Out Navbar Test', () => { + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('has the correct title', () => { + cy.title().should('equal', 'EasyWay'); + }); + + it('displays the correct title', () => { + cy.get('header') + .should('have.id','header') + .find('h3') + .find('a') + .should('have.attr','routerLink','/home') + .should('have.text','EasyWay') + }); + + it('has the nav element', () => { + cy.get('#header') + .find('nav') + .should('have.id','nav') + }); + + it('has the Home, Book a Service, Sign In and Get Started buttons', () => { + cy.get('#nav ul li') + .should('have.length', 4) + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('a') + .should('have.attr','href','index.html') + .should('have.text','Home') + } else if (index === 1) { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/services') + .should('have.attr','routerLinkActive','active') + .should('have.text','Book a Service') + } else if (index === 2) { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/login') + .should('have.attr','routerLinkActive','active') + .should('have.text','Sign In') + } else { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/register') + .should('have.attr','routerLinkActive','active') + .should('have.class','button') + .should('have.text','Get Started') + } + }) + + }) + +}) diff --git a/client/cypress/e2e/4. EasyWayRegistration.cy.ts b/client/cypress/e2e/4. EasyWayRegistration.cy.ts new file mode 100644 index 0000000..e3940ce --- /dev/null +++ b/client/cypress/e2e/4. EasyWayRegistration.cy.ts @@ -0,0 +1,172 @@ +describe('EasyWay Registration Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Get Started" button', () => { + cy.get('#nav a[routerLink="/register"]').click(); + }); + + it('goes to the User Registration page', () => { + cy.url() + .should('include','/register') + .should('equal','http://localhost:4200/register') + }); + + it('displays the correct Sign Up image', () => { + cy.get('.container .row .col-md-6') + .should('have.class','col-md-6 signup-img') + .find('img') + .should('have.class','img-fluid') + .should('have.attr','src') + .should('include','undraw_signup.svg') + }); + + it('displays the correct Registration form title', () => { + cy.get('.container .row .col-md-6') + .next() + .should('have.class','col-md-6 contents') + .find('.col-md-8 .mb-4 h3') + .should('contain.text','Sign Up') + }); + + it('displays the correct Registration form and fields', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.class','form-group mb-4 form-input-style') + .find('input') + .should('have.attr','type','text') + .should('have.attr','formControlName','name') + .should('have.attr','placeholder','Full Name') + } else if (index === 1) { + cy.wrap($el) + .should('have.class','form-group mb-4 form-input-style') + .find('input') + .should('have.attr','type','text') + .should('have.attr','formControlName','username') + .should('have.attr','placeholder','Username') + } else if (index === 2) { + cy.wrap($el) + .should('have.class','form-group mb-4 form-input-style') + .find('input') + .should('have.attr','type','email') + .should('have.attr','formControlName','email') + .should('have.attr','placeholder','E-mail') + } else if (index === 3) { + cy.wrap($el) + .should('have.class','form-group mb-4 form-input-style') + .find('select') + .should('have.id','gender-select') + .should('have.attr','formControlName','gender') + .find('option') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.value','') + .should('contain.text','Select gender') + } else if (index === 1) { + cy.wrap($el) + .should('have.value','M') + .should('contain.text','Male') + } else { + cy.wrap($el) + .should('have.value','F') + .should('contain.text','Female') + } + }) + } else if (index === 4){ + cy.wrap($el) + .should('have.class','form-group last mb-4 form-input-style') + .find('input') + .should('have.attr','type','password') + .should('have.attr','formControlName','password') + .should('have.attr','placeholder','Set Password') + } else { + cy.wrap($el) + .should('have.class','form-group last mb-4 form-input-style') + .find('input') + .should('have.attr','type','password') + .should('have.attr','formControlName','confirmPassword') + .should('have.attr','placeholder','Confirm Password') + } + }) + }); + + it('displays the registration form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .should('have.attr','type','submit') + .should('have.value','Sign Up') + .should('have.class','btn btn-block btn-primary') + }); + + it('fills the registration form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('Dummy User') + .should('have.value','Dummy User') + } else if (index === 1) { + cy.wrap($el) + .find('input') + .type('dummy1') + .should('have.value','dummy1') + } else if (index === 2) { + cy.wrap($el) + .find('input') + .type('dummy1@abc.com') + .should('have.value','dummy1@abc.com') + } else if (index === 3) { + cy.wrap($el) + .find('select') + .select('Male') + .should('have.value','M') + } else if (index === 4){ + cy.wrap($el) + .find('input') + .type('Dummy1_123') + .should('have.value','Dummy1_123') + } else { + cy.wrap($el) + .find('input') + .type('Dummy1_123') + .should('have.value','Dummy1_123') + } + }) + }); + + it('clicks the registration form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + + + + }); diff --git a/client/cypress/e2e/5. EasyWayLoginPage.cy.ts b/client/cypress/e2e/5. EasyWayLoginPage.cy.ts new file mode 100644 index 0000000..652de9e --- /dev/null +++ b/client/cypress/e2e/5. EasyWayLoginPage.cy.ts @@ -0,0 +1,112 @@ +describe('EasyWay Login Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Get Started" button', () => { + cy.get('#nav a[routerLink="/login"]').click(); + }); + + it('goes to the User Login page', () => { + cy.url() + .should('include','/login') + .should('equal','http://localhost:4200/login') + }); + + it('displays the correct Sign In image', () => { + cy.get('.container .row .col-md-6') + .should('have.class','col-md-6 div-mar') + .find('img') + .should('have.class','img-fluid') + .should('have.attr','src') + .should('include','undraw_sign_in.svg') + }); + + it('displays the correct login form title', () => { + cy.get('.container .row .col-md-6') + .next() + .should('have.class','col-md-6 contents div-mar') + .find('.col-md-8 .mb-4 h3') + .should('contain.text','Sign In') + }); + + it('displays the correct login form and fields', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.class','form-group first') + .find('input') + .should('have.attr','type','text') + .should('have.attr','formControlName','username') + .should('have.attr','placeholder','Username') + } else { + cy.wrap($el) + .should('have.class','form-group last mb-4') + .find('input') + .should('have.attr','type','password') + .should('have.attr','formControlName','password') + .should('have.attr','placeholder','Password') + } + }) + }); + + it('displays the login form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .should('have.attr','type','submit') + .should('have.value','Log In') + .should('have.class','btn btn-block btn-primary') + }); + + it('fills the login form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('dummy') + .should('have.value','dummy') + } else { + cy.wrap($el) + .find('input') + .type('dumdum') + .should('have.value','dumdum') + } + }) + }); + + it('clicks the form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + it('goes to the Services List page', () => { + cy.url() + .should('include','/services') + .should('equal','http://localhost:4200/services') + }); + +}); diff --git a/client/cypress/e2e/6. EasyWayNavbarLoggedIn.cy.ts b/client/cypress/e2e/6. EasyWayNavbarLoggedIn.cy.ts new file mode 100644 index 0000000..03b4534 --- /dev/null +++ b/client/cypress/e2e/6. EasyWayNavbarLoggedIn.cy.ts @@ -0,0 +1,112 @@ +describe('EasyWay Logged In Navbar Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Sign In" button', () => { + cy.get('#nav a[routerLink="/login"]').click(); + }); + + it('goes to the User Login page', () => { + cy.url() + .should('include','/login') + .should('equal','http://localhost:4200/login') + }); + + it('fills the login form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('dummy') + .should('have.value','dummy') + } else { + cy.wrap($el) + .find('input') + .type('dumdum') + .should('have.value','dumdum') + } + }) + }); + + it('clicks the form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + it('has the correct title', () => { + cy.title().should('equal', 'EasyWay'); + }); + + it('displays the correct title', () => { + cy.get('header') + .should('have.id','header') + .find('h3') + .find('a') + .should('have.attr','routerLink','/home') + .should('have.text','EasyWay') + }); + + it('has the nav element', () => { + cy.get('#header') + .find('nav') + .should('have.id','nav') + }); + + it('has the Home, Book a Service, My Bookings, Profile and Logout buttons', () => { + cy.get('#nav ul li') + .should('have.length', 3) + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('a') + .should('have.attr','href','index.html') + .should('have.text','Home') + } else if (index === 1) { + cy.wrap($el) + .find('a') + .should('have.attr','routerLink','/services') + .should('have.attr','routerLinkActive','active') + .should('have.text','Book a Service') + } else { + cy.wrap($el) + .find('a') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .should('have.attr','routerLink','/bookings') + .should('have.attr','routerLinkActive','active') + .should('have.text','My Bookings') + } else if (index === 1) { + cy.wrap($el) + .should('have.attr','routerLink','/profile') + .should('have.attr','routerLinkActive','active') + .should('have.text','Profile') + } else { + cy.wrap($el) + .should('have.attr','routerLink','/logout') + .should('have.attr','routerLinkActive','active') + .should('have.text','Logout') + } + }) + + } + + }) + + }) + +}) diff --git a/client/cypress/e2e/7. EasyWayServicesListPage.cy.ts b/client/cypress/e2e/7. EasyWayServicesListPage.cy.ts new file mode 100644 index 0000000..7e681c1 --- /dev/null +++ b/client/cypress/e2e/7. EasyWayServicesListPage.cy.ts @@ -0,0 +1,85 @@ +describe('EasyWay Services List Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Sign In" button', () => { + cy.get('#nav a[routerLink="/login"]').click(); + }); + + it('goes to the User Login page', () => { + cy.url() + .should('include','/login') + .should('equal','http://localhost:4200/login') + }); + + it('fills the login form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('dummy') + .should('have.value','dummy') + } else { + cy.wrap($el) + .find('input') + .type('dumdum') + .should('have.value','dumdum') + } + }) + }); + + it('clicks the form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + + + it('displays the correct page name', () => { + cy.get('header') + .should('have.class','bg-dark py-5 service-header') + .find('h1') + .should('have.class','display-4 fw-bolder') + .should('contain.text','Services List') + }); + + it('displays the list of services correctly along with "Book Now" button for each service', () => { + cy.get('section') + .should('have.id','main') + .should('have.class','container service-main') + .find('.row .col-6') + .each(($el, index, $list) => { + cy.wrap($el) + .should('have.class','col-6 col-12-narrower') + .find('section span') + .should('have.class','image featured') + .find('img') + .should('have.class','image-padding') + .parent() + .next() + .get('h3') + .next() + .get('p') + .next() + .should('have.class','actions special') + .find('li a') + .should('have.class','button alt') + .should('contain.text','Book Now') + }) + }); + +}); diff --git a/client/cypress/e2e/8. EasyWayServiceBookingPage.cy.ts b/client/cypress/e2e/8. EasyWayServiceBookingPage.cy.ts new file mode 100644 index 0000000..db21e32 --- /dev/null +++ b/client/cypress/e2e/8. EasyWayServiceBookingPage.cy.ts @@ -0,0 +1,175 @@ +var hour = Math.floor(Math.random() * 14 + 10) +var serviceNum = Math.floor(Math.random() * 5) + +describe('EasyWay Service Booking Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Sign In" button', () => { + cy.get('#nav a[routerLink="/login"]').click(); + }); + + it('goes to the User Login page', () => { + cy.url() + .should('include','/login') + .should('equal','http://localhost:4200/login') + }); + + it('fills the login form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('dummy') + .should('have.value','dummy') + } else { + cy.wrap($el) + .find('input') + .type('dumdum') + .should('have.value','dumdum') + } + }) + }); + + it('clicks the form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + + it('clicks "Book Now" button for one of the services', () => { + cy.get('section') + .find('.row .col-6') + .eq(serviceNum) + .find('section ul li a') + .click() + }); + + it('goes to the Service Booking page', () => { + cy.url() + .should('include','/bookService') + .should('include','service_id') + .should('include','service_name') + .should('include','service_price') + }); + + it('displays the correct Service Booking image', () => { + cy.get('.container .row .col-md-6') + .first() + .find('img') + .should('have.class','img-fluid booking-img') + .should('have.attr','src') + .should('include','undraw_booking.svg') + }); + + it('displays the service name and price', () => { + cy.get('.container .row .col-md-6') + .next() + .should('have.class','col-md-6 contents') + .find('.col-md-8 .mb-4 p') + .each(($el, index, $list) => { + if(index === 0) { + cy.wrap($el) + .should('have.class','mb-4 center-align booking-service-name') + .get('strong') + } else { + cy.wrap($el) + .should('have.class','mb-4 center-align booking-service-price') + .find('strong') + .should('contain.text', 'Price:') + } + }); + }); + + it('displays the correct service booking form', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .prev() + .should('contain.text','Enter date for service') + .next() + .should('have.class','form-group mb-4') + .find('input') + .should('have.attr','type','date') + .should('have.attr','formControlName','date') + } else { + cy.wrap($el) + .prev() + .should('contain.text','Enter time for service') + .next() + .should('have.class','form-group mb-4') + .find('input') + .should('have.attr','type','time') + .should('have.attr','formControlName','time') + } + }) + }); + + it('displays the service booking form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .should('have.attr','type','submit') + .should('have.value','Book') + .should('have.class','btn btn-block btn-primary') + }); + + it('fills the service booking form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('2022-04-22') + .should('have.value','2022-04-22') + } else { + cy.wrap($el) + .find('input') + .type(hour + ':45') + .should('have.value',hour + ':45') + } + }) + }); + + it('clicks the service booking form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + +}); diff --git a/client/cypress/e2e/9. EasyWayMyBookingsPage.cy.ts b/client/cypress/e2e/9. EasyWayMyBookingsPage.cy.ts new file mode 100644 index 0000000..9d1e8df --- /dev/null +++ b/client/cypress/e2e/9. EasyWayMyBookingsPage.cy.ts @@ -0,0 +1,158 @@ +describe('EasyWay My Bookings Page Test', () => { + + before(() => { + cy.visit('http://localhost:4200/'); + }); + + it('clicks on the "Sign In" button', () => { + cy.get('#nav a[routerLink="/login"]').click(); + }); + + it('goes to the User Login page', () => { + cy.url() + .should('include','/login') + .should('equal','http://localhost:4200/login') + }); + + it('fills the login form correctly', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .form-group') + .each(($el, index, $list) => { + if (index === 0) { + cy.wrap($el) + .find('input') + .type('dummy') + .should('have.value','dummy') + } else { + cy.wrap($el) + .find('input') + .type('dumdum') + .should('have.value','dumdum') + } + }) + }); + + it('clicks the form submit button', () => { + cy.get('.container .row .col-md-6') + .next() + .find('.col-md-8 .mb-4') + .nextAll() + .first() + .get('form .center-align') + .find('input') + .click() + }); + + it('clicks on the "My Bookings" button', () => { + cy.get('#nav ul li a[routerLink="/bookings"]').click(); + }); + + it('goes to the My Bookings page', () => { + cy.url() + .should('include','/bookings') + .should('equal','http://localhost:4200/bookings') + }); + + it('displays the correct page name', () => { + cy.get('header') + .should('have.class','bg-dark py-5 service-header') + .find('h1') + .should('have.class','display-4 fw-bolder') + .should('contain.text','My Bookings') + }); + + it('displays the list of "Active" bookings correctly along with "Cancel" button for each booking', () => { + cy.get('section .container') + .children('h3') + .first() + .should('contain.text','Active Bookings') + .parent() + .children('section') + .first() + .should('have.id','active-main') + .children('div') + .should('have.class','row') + .find('section') + .should('have.length.at.least', 1) + .each(($el, index, $list) => { + cy.wrap($el) + .parent() + .should('have.class','col-6 col-12-narrower') + .find('section') + .should('have.class','box special') + .children('h3') + .parent() + .children('h5') + .parent() + .children('p') + .parent() + .find('ul li a') + .should('have.class','button alt btn btn-danger') + .should('contain.text','Cancel') + }) + + }); + + it('clicks "Cancel" button for one of the bookings', () => { + cy.get('#active-main') + .find('.row .col-6') + .first() + .find('section ul li a') + .click() + }); + + it('displays the list of "Cancelled" bookings correctly', () => { + cy.get('section .container') + .children('h3') + .eq(1) + .should('contain.text','Cancelled bookings') + .next() + .should('have.id','cancel-main') + .children('div') + .find('section') + .should('have.length.at.least', 1) + .each(($el, index, $list) => { + cy.wrap($el) + .parent() + .should('have.class','col-6 col-12-narrower') + .find('section') + .should('have.class','box special') + .children('h3') + .parent() + .children('h5') + .parent() + .children('p') + }) + + }); + + it('displays the "Book a Service" button', () => { + cy.get('section .container') + .children() + .eq(4) + .should('have.class','center-align book-btn') + .find('button') + .find('a') + .should('have.attr','href','/services') + .should('contain.text','Book a Service') + }); + + it('clicks the "Book a Service" button', () => { + cy.get('section .container') + .children() + .eq(4) + .find('button') + .click() + }); + + it('goes to the Services List page', () => { + cy.url() + .should('include','/services') + .should('equal','http://localhost:4200/services') + }); + +}); diff --git a/client/cypress/fixtures/example.json b/client/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/client/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/client/cypress/integration/1-getting-started/todo.spec.js b/client/cypress/integration/1-getting-started/todo.spec.js new file mode 100644 index 0000000..4768ff9 --- /dev/null +++ b/client/cypress/integration/1-getting-started/todo.spec.js @@ -0,0 +1,143 @@ +/// + +// Welcome to Cypress! +// +// This spec file contains a variety of sample tests +// for a todo list app that are designed to demonstrate +// the power of writing tests in Cypress. +// +// To learn more about how Cypress works and +// what makes it such an awesome testing tool, +// please read our getting started guide: +// https://on.cypress.io/introduction-to-cypress + +describe('example to-do app', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('https://example.cypress.io/todo') + }) + + it('displays two todo items by default', () => { + // We use the `cy.get()` command to get all elements that match the selector. + // Then, we use `should` to assert that there are two matched items, + // which are the two default items. + cy.get('.todo-list li').should('have.length', 2) + + // We can go even further and check that the default todos each contain + // the correct text. We use the `first` and `last` functions + // to get just the first and last matched elements individually, + // and then perform an assertion with `should`. + cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') + cy.get('.todo-list li').last().should('have.text', 'Walk the dog') + }) + + it('can add new todo items', () => { + // We'll store our item text in a variable so we can reuse it + const newItem = 'Feed the cat' + + // Let's get the input element and use the `type` command to + // input our new list item. After typing the content of our item, + // we need to type the enter key as well in order to submit the input. + // This input has a data-test attribute so we'll use that to select the + // element in accordance with best practices: + // https://on.cypress.io/selecting-elements + cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) + + // Now that we've typed our new item, let's check that it actually was added to the list. + // Since it's the newest item, it should exist as the last element in the list. + // In addition, with the two default items, we should have a total of 3 elements in the list. + // Since assertions yield the element that was asserted on, + // we can chain both of these assertions together into a single statement. + cy.get('.todo-list li') + .should('have.length', 3) + .last() + .should('have.text', newItem) + }) + + it('can check off an item as completed', () => { + // In addition to using the `get` command to get an element by selector, + // we can also use the `contains` command to get an element by its contents. + // However, this will yield the