Compare commits

..

2 Commits

Author SHA1 Message Date
Dave Richer
239d034923 - Release commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-22 12:48:14 -04:00
Dave Richer
91284ba405 Merge remote-tracking branch 'origin/release/2024-03-22' into feature/IO-1828-Front-End-Package-Updates 2024-03-19 16:26:40 -04:00
1436 changed files with 135830 additions and 151577 deletions

View File

@@ -5,9 +5,8 @@ orbs:
aws-s3: circleci/aws-s3@4.0.0 aws-s3: circleci/aws-s3@4.0.0
aws-cli: circleci/aws-cli@4.0 aws-cli: circleci/aws-cli@4.0
eb: circleci/aws-elastic-beanstalk@2.0.1 eb: circleci/aws-elastic-beanstalk@2.0.1
jira: circleci/jira@2.1.0
jobs: jobs:
imex-api-deploy: api-deploy:
docker: docker:
- image: cimg/node:18.18.2 - image: cimg/node:18.18.2
steps: steps:
@@ -19,16 +18,10 @@ jobs:
eb status --verbose eb status --verbose
eb deploy eb deploy
eb status eb status
- jira/notify:
environment: Production (ImEX) - API
environment_type: production
job_type: deployment
pipeline_id: << pipeline.id >>
pipeline_number: << pipeline.number >>
imex-hasura-migrate: hasura-migrate:
docker: docker:
- image: cimg/node:18.18.2 - image: cimg/node:16.15.0
parameters: parameters:
secret: secret:
type: string type: string
@@ -40,21 +33,17 @@ jobs:
- run: - run:
name: Execute migration name: Execute migration
command: | command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash npm install hasura-cli -g
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >> hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >> hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >> hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Production (ImEX) - Hasura app-build:
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
imex-app-build:
docker: docker:
- image: cimg/node:18.18.2 - image: cimg/node:16.15.0
resource_class: large resource_class: large
working_directory: ~/repo/client working_directory: ~/repo/client
steps: steps:
- checkout: - checkout:
path: ~/repo path: ~/repo
@@ -62,7 +51,27 @@ jobs:
name: Install Dependencies name: Install Dependencies
command: npm i command: npm i
- run: npm run build:imex - run: npm run build
- aws-s3/sync:
from: build
to: "s3://imex-online-production/"
arguments: "--exclude '*.map'"
app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build
- aws-cli/setup: - aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID aws_access_key_id: AWS_ACCESS_KEY_ID
@@ -71,237 +80,12 @@ jobs:
- aws-s3/sync: - aws-s3/sync:
from: build from: build
to: "s3://imex-online-production/"
arguments: "--exclude '*.map'"
imex-app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:production:imex
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: dist
to: "s3://imex-online-beta/" to: "s3://imex-online-beta/"
arguments: "--exclude '*.map'" arguments: "--exclude '*.map'"
- jira/notify:
environment: Production (ImEX) - Front End
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
rome-api-deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
- jira/notify:
environment: Production (Rome) - API
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
rome-hasura-migrate:
docker:
- image: cimg/node:18.18.2
parameters:
secret:
type: string
default: $HASURA_ROME_PROD_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Production (Rome) - Hasura
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
rome-app-build:
docker:
- image: cimg/node:18.18.2
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:production:rome
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: dist
to: "s3://rome-online-production/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Production (Rome) - Front End
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
promanager-app-build:
docker:
- image: cimg/node:18.18.2
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:production:promanager
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: dist
to: "s3://promanager-production/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Production (ProManager) - Front End
environment_type: production
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-rome-hasura-migrate:
docker:
- image: cimg/node:18.18.2
parameters:
secret:
type: string
default: $HASURA_ROME_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
sleep 5
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
sleep 10
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Test (Rome) - Hasura
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-rome-app-build:
docker:
- image: cimg/node:18.18.2
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test:rome
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: dist
to: "s3://rome-online-test/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Test (Rome) - Front End
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-promanager-app-build:
docker:
- image: cimg/node:18.18.2
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test:promanager
- aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: AWS_REGION
- aws-s3/sync:
from: dist
to: "s3://promanager-testing/"
arguments: "--exclude '*.map'"
- jira/notify:
environment: Test (ProManager) - Front End
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
test-hasura-migrate: test-hasura-migrate:
docker: docker:
- image: cimg/node:18.18.2 - image: cimg/node:16.15.0
parameters: parameters:
secret: secret:
type: string type: string
@@ -313,22 +97,15 @@ jobs:
- run: - run:
name: Execute migration name: Execute migration
command: | command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >> hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
sleep 15
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >> hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
sleep 30
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >> hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
- jira/notify:
environment: Test (ImEX) - Hasura
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
imex-test-app-build: test-app-build:
docker: docker:
- image: cimg/node:18.18.2 - image: cimg/node:16.15.0
resource_class: large resource_class: large
working_directory: ~/repo/client working_directory: ~/repo/client
@@ -339,14 +116,14 @@ jobs:
name: Install Dependencies name: Install Dependencies
command: npm i command: npm i
- run: npm run build:test:imex - run: npm run build:test
- aws-s3/sync: - aws-s3/sync:
from: build from: build
to: "s3://imex-online-test/" to: "s3://imex-online-test/"
arguments: "--exclude '*.map'" arguments: "--exclude '*.map'"
imex-test-app-beta-build: test-app-beta-build:
docker: docker:
- image: cimg/node:18.18.2 - image: cimg/node:18.18.2
resource_class: large resource_class: large
@@ -360,7 +137,9 @@ jobs:
name: Install Dependencies name: Install Dependencies
command: npm i command: npm i
- run: npm run build:test:imex - run: npm run build:test
- run: npm run sentry:sourcemaps
- aws-cli/setup: - aws-cli/setup:
aws_access_key_id: AWS_ACCESS_KEY_ID aws_access_key_id: AWS_ACCESS_KEY_ID
@@ -368,15 +147,9 @@ jobs:
region: AWS_REGION region: AWS_REGION
- aws-s3/sync: - aws-s3/sync:
from: dist from: build
to: "s3://imex-online-test-beta/" to: "s3://imex-online-test-beta/"
arguments: "--exclude '*.map'" arguments: "--exclude '*.map'"
- jira/notify:
environment: Test (ImEX) - Front End
environment_type: testing
pipeline_id: << pipeline.id >>
job_type: deployment
pipeline_number: << pipeline.number >>
admin-app-build: admin-app-build:
docker: docker:
@@ -411,66 +184,36 @@ jobs:
workflows: workflows:
deploy_and_build: deploy_and_build:
jobs: jobs:
- imex-api-deploy: - api-deploy:
filters:
branches:
only: master-AIO
- imex-app-build:
filters: filters:
branches: branches:
only: master only: master
- imex-app-beta-build: - app-build:
filters: filters:
branches: branches:
only: master-AIO only: master
- imex-hasura-migrate: - app-beta-build:
filters:
branches:
only: master-beta
- hasura-migrate:
secret: ${HASURA_PROD_SECRET} secret: ${HASURA_PROD_SECRET}
filters: filters:
branches: branches:
only: master-AIO only: master
- rome-api-deploy: - test-app-build:
filters:
branches:
only: master-AIO
- rome-app-build:
filters:
branches:
only: master-AIO
- rome-hasura-migrate:
secret: ${HASURA_ROME_PROD_SECRET}
filters:
branches:
only: master-AIO
- imex-test-app-build:
filters: filters:
branches: branches:
only: test only: test
- imex-test-app-beta-build: - test-app-beta-build:
filters: filters:
branches: branches:
only: test-AIO only: test-beta
- test-hasura-migrate: - test-hasura-migrate:
secret: ${HASURA_TEST_SECRET} secret: ${HASURA_TEST_SECRET}
filters: filters:
branches: branches:
only: test-AIO only: test
- test-rome-app-build:
filters:
branches:
only: test-AIO
- test-promanager-app-build:
filters:
branches:
only: test-AIO
- promanager-app-build:
filters:
branches:
only: master-AIO
- test-rome-hasura-migrate:
secret: ${HASURA_ROME_TEST_SECRET}
filters:
branches:
only: test-AIO
#- admin-app-build: #- admin-app-build:
#filters: #filters:
#branches: #branches:

View File

@@ -1,24 +0,0 @@
# Directories to exclude
.circleci
.idea
.platform
.vscode
_reference
client
redis/dockerdata
hasura
node_modules
# Files to exclude
.ebignore
.editorconfig
.eslintrc.json
.gitignore
.prettierrc.js
Dockerfile
README.MD
bodyshop_translations.babel
docker-compose.yml
ecosystem.config.js
# Optional: Exclude logs and temporary files
*.log

View File

@@ -1,21 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = false
quote_type = single
max_line_length = 100
bracketSpacing = false
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
[*.json]
indent_size = 2

1
.gitignore vendored
View File

@@ -21,7 +21,6 @@ admin/coverage
/build /build
client/build client/build
admin/build admin/build
client/dist
# misc # misc
.DS_Store .DS_Store
.env .env

View File

@@ -1,18 +1,16 @@
const config = { exports.default = {
printWidth: 120, printWidth: 120,
useTabs: false, useTabs: false,
tabWidth: 2, tabWidth: 2,
trailingComma: "none", trailingComma: "es5",
semi: true, semi: true,
singleQuote: false, singleQuote: false,
bracketSpacing: true, bracketSpacing: true,
arrowParens: "always", arrowParens: "always",
jsxSingleQuote: false, jsxSingleQuote: false,
bracketSameLine: false, bracketSameLine: false,
endOfLine: "lf" endOfLine: "lf",
// importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
// importOrderSeparation: true, importOrderSeparation: true,
// importOrderSortSpecifiers: true importOrderSortSpecifiers: true,
}; };
module.exports = config;

15
.vscode/launch.json vendored
View File

@@ -14,21 +14,6 @@
"request": "launch", "request": "launch",
"url": "http://localhost:3000", "url": "http://localhost:3000",
"webRoot": "${workspaceRoot}/client/src" "webRoot": "${workspaceRoot}/client/src"
},
{
"name": "Attach to Node.js in Docker",
"type": "node",
"request": "attach",
"address": "localhost",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app",
"protocol": "inspector",
"restart": true,
"sourceMaps": true,
"skipFiles": [
"<node_internals>/**"
]
} }
] ]
} }

View File

@@ -1,47 +0,0 @@
# Use Amazon Linux 2023 as the base image
FROM amazonlinux:2023
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
RUN dnf install -y git \
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
&& dnf install -y nodejs \
&& dnf clean all
# Install dependencies required by node-canvas
RUN dnf install -y \
gcc \
gcc-c++ \
cairo-devel \
pango-devel \
libjpeg-turbo-devel \
giflib-devel \
libpng-devel \
make \
python3 \
python3-pip \
&& dnf clean all
# Set the working directory
WORKDIR /app
# This is because our test route uses a git commit hash
RUN git config --global --add safe.directory /app
# Copy package.json and package-lock.json
COPY package.json ./
# Install Nodemon
RUN npm install -g nodemon
# Install dependencies
RUN npm i --no-package-lock
# Copy the rest of your application code
COPY . .
# Expose the port your app runs on (adjust if necessary)
EXPOSE 4000 9229
# Start the application
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]

View File

@@ -2,7 +2,7 @@ NGROK TEsting:
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000" ./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
Finding deadfiles - run from client directory Finding deadfiles - run from client directory
npx deadfile ./src/index.jsx --exclude build templates npx deadfile ./src/index.js --exclude build templates
#Crushing all hasura migrations by creating a new initialization from the server. #Crushing all hasura migrations by creating a new initialization from the server.
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#' hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'

View File

@@ -1,14 +0,0 @@
{
"mobile": "date",
"allAccess": "bool",
"timetickets": "date",
"payments": "date",
"partsorders": "date",
"bills": "Date",
"export": "date",
"csi": "Date",
"courtesycars": "date",
"media": "date",
"visualboard": "date",
"scoreboard": "date"
}

View File

@@ -1,64 +0,0 @@
# Setting up External Networking and Static IP for WSL2 using Hyper-V
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
## Prerequisites
1. **Windows 11**
2. **Docker Desktop For Windows (Latest Version)
# Docker Setup
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
`docker-compose up` to launch the backend.
Things to note:
- When installing NPM packages, you will need to rebuild the `node-app` container
- Making changes to the server files will restart the `node-app`
# Local Stack
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
- http://localhost:4566/_aws/ses will allow you to see emails sent
# Docker Commands
## General `docker-compose` Commands:
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
4. Stop containers without removing them: `docker-compose stop`
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
6. Force rebuild of containers: `docker-compose build --no-cache`
7. View running Containers: `docker-compose ps`
8. View a specific containers logs: `docker-compose logs <container-name>`
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
## Volume Management Commands
1. List Docker volumes: `docker volume ls`
2. Remove Unused volumes `docker volume prune`
3. Remove specific volumes `docker volume rm <volume-name>`
4. Inspect a volume: `docker volume inspect <volume-name>`
## Container Image Management Commands:
1. List running containers: `docker ps`
2. List all containers: `docker os -a`
3. Remove Stopped containers: `docker container prune`
4. Remove a specific container: `docker container rm <container-name>`
5. Remove a specific image: `docker rmi <image-name>:<version>`
6. Remove all unused images: `docker image prune -a`
## Network Management Commands:
1. List networks: `docker network ls`
2. Inspect a specific network: `docker network inspect <network-name>`
3. Remove a specific network: `docker network rm <network-name>`
4. Remove unused networks: `docker network prune`
## Debugging and maintenance:
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
2. View container resource usage: `docker stats`
3. Check Disk space used by Docker: `docker system df`
4. Remove all unused Data (Nuclear option): `docker system prune`
## Specific examples
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down

View File

@@ -1,59 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IMEX IO Extractor</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
textarea {
width: 100%;
height: 200px;
}
.output-box {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
min-height: 40px;
}
.copy-button {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>IMEX IO Extractor</h1>
<textarea id="inputText" placeholder="Paste your text here..."></textarea>
<br>
<button onclick="extractIO()">Extract</button>
<div class="output-box" id="outputBox" contenteditable="true"></div>
<button class="copy-button" onclick="copyToClipboard()">Copy to Clipboard</button>
<script>
function extractIO() {
const inputText = document.getElementById('inputText').value;
const ioNumbers = [...new Set(inputText.match(/IO-\d{4}/g))] // Extract unique IO-#### matches
.map(io => ({ io, num: parseInt(io.split('-')[1]) })) // Extract number part for sorting
.sort((a, b) => a.num - b.num) // Sort by the number
.map(item => item.io); // Extract sorted IO-####
document.getElementById('outputBox').innerText = ioNumbers.join(', '); // Display horizontally
}
function copyToClipboard() {
const outputBox = document.getElementById('outputBox');
const range = document.createRange();
range.selectNodeContents(outputBox);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('copy');
}
</script>
</body>
</html>

View File

@@ -1,41 +0,0 @@
# Production Board Notes:
## General Notes
- You can single click the lane footer to collapse/un-collapse the lane
- You can double click the lane header to collapse/un-collapse the lane
- If you need to scroll horizontally, you can hold shift and use the mouse scroll wheel, or press the mouse scroll wheel while scrolling
## Board Settings
#### Layout
- Board Orientation (Vertical or Horizontal)
- This determines the orientation of the card layout on the board.
- Horizontal is the default setting, and how the prior board was set up.
- Vertical is the new setting and allows lanes to be displayed vertically, with a grid of cards
- Card Size (Small, Medium, Large)
- This determines the size of the cards on the board.
- Small is the default setting, and how the prior board was set up.
- Medium and Large are new settings and allow for larger cards to be displayed on the board.
- Compact Cards (Tall or Wide)
- Formally called 'Compact'
- When on, data is displayed on the card vertically
- when turned off, some fields may share horizontal space, tightening the card layout
- Colored Cards (On or Off)
- When on, cards are colored based on the Status color
- Kiosk Mode (On or Off)
- This should be turned on if the shop is using it on a tablet (Ipad)
#### Information
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
### Statistics
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
### Filters
- Allows you to set, and persist filters for estimators and insurance companies

View File

@@ -4,7 +4,7 @@ Clone Repository for:
{ {
"name": "node-webhook-scripts", "name": "node-webhook-scripts",
"version": "1.0.0", "version": "1.0.0",
"main": "index.jsx", "main": "index.js",
"dependencies": { "dependencies": {
"express": "^4.16.4" "express": "^4.16.4"
}, },

View File

@@ -1,20 +1,20 @@
module.exports = { module.exports = {
apps: [ apps: [
{ {
name: "IO Test API", name: "IO Test API",
cwd: "./io", cwd: "./io",
script: "./server.js", script: "./server.js",
env: { env: {
NODE_ENV: "test" NODE_ENV: "test",
} },
}, },
{ {
name: "Bitbucket Webhook", name: "Bitbucket Webhook",
script: "./webhook/index.jsx", script: "./webhook/index.js",
env: { env: {
NODE_ENV: "production" NODE_ENV: "production",
} },
} },
] ],
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDWzCCAkOgAwIBAgIUD/QBSAXy/AlJ/cS4DaPWJLpChxgwDQYJKoZIhvcNAQEL
BQAwPTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSEwHwYDVQQKDBhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA5MTU0MjA1WhcNMjUwOTA5MTU0MjA1
WjA9MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKSd0l7NJCNBwvtPU+dVPQkteg0AfC3sGqRnZMQteCRVa2oIgC4NoF3A9BK/yHbF
ZF25OnXTck5vzc8yb3v73ndfTD9ASKNoiaZE84/GFBsxqlKR8cs0qVwzuAsdijMv
vlMPNlMRyE1Rb7nR6HXGkPXNyxgMko03NXPkvIje9zRudm0Lf8L4q/hPyPkS7Mrm
/uQfAAJe+xFcupkEX2XY7r0x1C+z6E8lA1UcuhK3SHdW7CWYqp1vU5/dnnUiXwCa
GiC6Y1bCJB0pDAVISzy3JUDdINZdiqGR+y8ho3pstChf2mp/76s3N9eG9KA/qaFK
BrGk2PvCoZ8/Aj1aMsRYFHECAwEAAaNTMFEwHQYDVR0OBBYEFDLJ2fbWP4VUJgOp
PSs+NGHcVgRmMB8GA1UdIwQYMBaAFDLJ2fbWP4VUJgOpPSs+NGHcVgRmMA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABfv5ut/y03atq0NMB0jeDY4
AvW4ukk0k1svyqxFZCw9o7m2lHb/IjmVrZG1Sj4JWrrSv0s02ccb26/t6vazNa5L
Powe3eyfHgfjTZJmgs8hyeMwKS0wWk/SPuu9JDhIJakiquqD+UVBGkHpP+XYvhDv
vhS2XRlW+aEjpUmr1oCyyrc6WbzrYRNadqEsn/AxwcMyUbht3Ugjkg+OpidcTIQp
5lv63waKo6I1vQofzBQ3L7JYsKo8kC0vAP7wkLxvzBii335uZJzzpFYFVOyVNezi
dJdazPbRYbXz4LjltdEn/SNfRuKX8ZRiN2OSo7OfSrZaMTS87SfCSFJGgQM8Yrk=
-----END CERTIFICATE-----

View File

@@ -1,27 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAvNl5fuVmLNv72BZNxnTqX5CHf5Xi8UxjYaYxHITSCx7blnhpVYLd
qXvcOWXzbsfjch/den73QiW4n2FYz75oGMhUGlOYzdWKA9I9Sj09Qy1R06RhwDiZGd5qaM
swEeXpkNmi2u4Qd2kJeDfUQUigjC09V81O/vrniGtQAJScfiG/itdm+Ufn09Z4MYk0HWjq
iDokNEskoEPsibYIrb+Q6vdtuPkZO+wU/smXhPtgw5ST6oQdmm/gVNsRg5XNzxrire+z1G
WatnnVL3hPnnfpnf8W589dyms7GGJwhPerSGTN1bn0T4+9C69Cd7LBJtxiuFdRmdlGLLLP
RR48Rur71wAAA9AEfVsdBH1bHQAAAAdzc2gtcnNhAAABAQC82Xl+5WYs2/vYFk3GdOpfkI
d/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN
1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1
AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DD
lJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3Vuf
RPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvXAAAAAwEAAQAAAQAQTosSLQbMmtY9S3e9
yjyusdExcCTfhyQRu4MEHmfws+JsNMuLqbgwOVTD1AzYJQR7x0qdmDcLjCxL/uDnV16vvS
Sd/Vf1dhnryIyoS29tzI0DRG94ZKq7tBvmHp1w/jRT4KcSVnovhW9e5Rs74+SRFhr06PKI
S+wQOIv48Nwue9+QUMsMCpWgKXHx7SHNTHvnAfqdhi9O29SWlMA+v+mELZ5Cl+HU0UTt2I
A1BxOe1N8FjN7KE2viJexsl3is1PuqMkpLl/wyHBJTVzUadl6DRALJQIm7/YO5goE72YOV
Lpo27do3zjhC87dlKdATvZUzfKV0LuUVdxq/PNDZMUbBAAAAgQDShAqDZiDrdTUaGXfUVm
QzcnVNbh2/KgZh4uux9QNHST562W6cnN7qxoRwVrM4BCOk1Kl73QQZW4nDvXX3PVC5j038
8AXkcBHS9j9f4h72ue7D2jqlbHFa7aGU9zYgk9mbBF+GX3tDntkAIQjLtwOLfj1iiJ/clX
mHFUAY1V4L8AAAAIEA3E4t/v0yU5D9AOI0r17UNYqfeyDoKAEDR4QbbFjO1l0kLnEJy7Zx
Mhj18GilYg2y0P0v8dSM/oWXS8Hua2t5i9Exlv6gHhGlQ80mwYcVGIxewZ/pPeCPw0U+kt
EKUjt09m9Oe7+6xHQsTBj9hY8/vqPmQwRalZFcLdhHiDiVKTcAAACBANtykaPXdVzEFx7D
UOlsjVL7zM0EVOFXf9JJQ6BhazhmsEI2PYt3IpgGMo8cXkoUofAOIYjf421AabN1BqSO5J
XTMxM0ZV3JmLLi804Mu9h1iFrVTBdLYOMJdc2VCo1EwHWpo9SXOyjxce/znvcIOU04aZhu
TaPg816X+E+gw5JhAAAAFGRhdmVARGF2ZVJpY2hlci1JTUVYAQIDBAUG
-----END OPENSSH PRIVATE KEY-----

View File

@@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC82Xl+5WYs2/vYFk3GdOpfkId/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DDlJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3VufRPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvX dave@DaveRicher-IMEX

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkndJezSQjQcL7
T1PnVT0JLXoNAHwt7BqkZ2TELXgkVWtqCIAuDaBdwPQSv8h2xWRduTp103JOb83P
Mm97+953X0w/QEijaImmRPOPxhQbMapSkfHLNKlcM7gLHYozL75TDzZTEchNUW+5
0eh1xpD1zcsYDJKNNzVz5LyI3vc0bnZtC3/C+Kv4T8j5EuzK5v7kHwACXvsRXLqZ
BF9l2O69MdQvs+hPJQNVHLoSt0h3VuwlmKqdb1Of3Z51Il8AmhogumNWwiQdKQwF
SEs8tyVA3SDWXYqhkfsvIaN6bLQoX9pqf++rNzfXhvSgP6mhSgaxpNj7wqGfPwI9
WjLEWBRxAgMBAAECggEAUNpHYlLFxh9dokujPUMreF+Cy/IKDBAkQc2au5RNpyLh
YDIOqw/8TTAhcTgLQPLQygvZP9f8E7RsVLFD+pSJ/v2qmIJ9au1Edor1Sg+S/oxV
SLrwFMunx2aLpcH7iAqSI3+cQg7A3+D4zD7iOz6tIl3Su9wo+v073tFhHKTOrEwv
Qgr9Jf3viIiKV1ym+uQEVQndocfsj46FnFpXTQ2qs7kAF6FgAOLDGfQQwzkiqEBD
NsqsDmbYIx6foZL+DEz1ZVO2M5B+xxpbNK82KwuQilVpimW8ui4LZHCe+RIFzt9+
BK6KGlLpSEwTFliivI3nahy18JzskZsfyah0CPZlQQKBgQDVv+A0qIPGvOP3Sx+9
HyeQCV23SkvvSvw8p8pMB0gvwv63YdJ7N/rJzBGS6YUHFWWZZgEeTgkJ6VJvoe0r
8JL1el9uSUa7f0eayjmFBOGuzpktNVdIn2Tg7A9MWA4JqPNNC69RMOh86ewGD4J3
a8Hz2a1bHxAmy/AZt2ukypY6eQKBgQDFJ7kqeOPkRBz9WbALRgVIXo8YWf5di0sQ
r0HC03GAISHQ725A2IFBPHJWeqj0jaMiIZD0y+Obgp7KAskrJaLfsd7Ug775kFfw
oUI9UAl6kRuPKvm3BaVAm46SQm+56VsgxTi73YN0NUp75THHZgAJjepF9zSpVJxq
VY9DjEGruQKBgQCQCpGIatcCol/tUg69X7VFd0pULhkl1J5OMbQ9r9qRdRI5eg5h
QsQaIQ7mtb8TmvOwf/DY/zVQHI+U8sXlCmW+TwzoQTENQSR7xzMj1LpRFqBaustr
AR72A537kItFLzll/i3SxOam5uxK2UDOQSuerF4KPdCglGXkrpo3nt3F4QKBgQCa
RArPAOjQo7tLQfJN3+wiRFsTYtd1uphx5bA/EdOtvj8HjVFnzADXWsTchf3N3UXY
XwtdgGwIMpys1KEz8a8P+c2x26SDAj7NOmDqOMYx8Xju/WGHpBM6Cn30U6e4gK+d
ZLSPyzQgqdIuP5hDvbwpvbGiLVw3Ys1BJtGCuSxpgQJ/eHnRiuSi5Zq5jGg+GpA+
FEEc9NCy772rR+4uzEOqyIwqewffqzSuVWuIsY/8MP3fh+NDxl/mU6cB5QVeD54Z
JZUKwmpM26muiM6WvVnM4ExPdSGA2+l4pZjby/KKd6F/w0tgZ1jb9Pb2/0vN3qVA
Y4U4XNTMt2fxUACqiL4SHA==
-----END PRIVATE KEY-----

13
client/.env.development Normal file
View File

@@ -0,0 +1,13 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -1,14 +0,0 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=/api/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX

View File

@@ -1,14 +0,0 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=/api/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=PROMANAGER

View File

@@ -1,16 +0,0 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
VITE_APP_GA_CODE=231099835
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=/api/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_COUNTRY=USA
VITE_APP_INSTANCE=ROME

14
client/.env.production Normal file
View File

@@ -0,0 +1,14 @@
GENERATE_SOURCEMAP=true
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk

View File

@@ -1,15 +0,0 @@
GENERATE_SOURCEMAP=true
VITE_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
VITE_APP_GA_CODE=231103507
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
VITE_APP_CLOUDINARY_API_KEY=473322739956866
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=https://api.imex.online/
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=IMEX

View File

@@ -1,15 +0,0 @@
GENERATE_SOURCEMAP=true
VITE_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
VITE_APP_GA_CODE=231103507
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
VITE_APP_CLOUDINARY_API_KEY=473322739956866
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=PROMANAGER

View File

@@ -1,15 +0,0 @@
GENERATE_SOURCEMAP=true
VITE_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
VITE_APP_GA_CODE=231103507
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
VITE_APP_CLOUDINARY_API_KEY=473322739956866
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=ROME

14
client/.env.test Normal file
View File

@@ -0,0 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -1,15 +0,0 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
VITE_APP_CLOUDINARY_API_KEY=473322739956866
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX

View File

@@ -1,15 +0,0 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
VITE_APP_CLOUDINARY_API_KEY=473322739956866
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=PROMANAGER

View File

@@ -1,15 +0,0 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
VITE_APP_GA_CODE=231103507
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
VITE_APP_CLOUDINARY_API_KEY=473322739956866
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=ROME

View File

@@ -1,8 +0,0 @@
{
"extends": [
"react-app"
],
"rules": {
"no-useless-rename": "off"
}
}

1
client/.gitignore vendored
View File

@@ -1,4 +1,3 @@
# Sentry Config File # Sentry Config File
.sentryclirc .sentryclirc
/dev-dist

68
client/craco.config.js Normal file
View File

@@ -0,0 +1,68 @@
// craco.config.js
const TerserPlugin = require("terser-webpack-plugin");
const CracoLessPlugin = require("craco-less");
const {convertLegacyToken} = require('@ant-design/compatible/lib');
const {theme} = require('antd/lib');
const {defaultAlgorithm, defaultSeed} = theme;
const mapToken = defaultAlgorithm(defaultSeed);
const v4Token = convertLegacyToken(mapToken);
// TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely.
module.exports = {
plugins: [
// {
// plugin: SentryWebpackPlugin,
// options: {
// // sentry-cli configuration
// authToken:
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
// org: "snapt-software",
// project: "imexonline",
// release: process.env.REACT_APP_GIT_SHA,
//
// // webpack-specific configuration
// include: ".",
// ignore: ["node_modules", "webpack.config.js"],
// },
// },
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {...v4Token},
javascriptEnabled: true,
},
},
},
},
],
webpack: {
configure: (webpackConfig) => {
return {
...webpackConfig,
// Required for Dev Server
devServer: {
...webpackConfig.devServer,
allowedHosts: 'all',
},
optimization: {
...webpackConfig.optimization,
// Workaround for CircleCI bug caused by the number of CPUs shown
// https://github.com/facebook/create-react-app/issues/8320
minimizer: webpackConfig.optimization.minimizer.map((item) => {
if (item instanceof TerserPlugin) {
item.options.parallel = 2;
}
return item;
}),
},
};
},
},
devtool: "source-map",
};

View File

@@ -1,17 +1,17 @@
const { defineConfig } = require("cypress"); const {defineConfig} = require('cypress')
module.exports = defineConfig({ module.exports = defineConfig({
experimentalStudio: true, experimentalStudio: true,
env: { env: {
FIREBASE_USERNAME: "cypress@imex.test", FIREBASE_USERNAME: 'cypress@imex.test',
FIREBASE_PASSWORD: "cypress" FIREBASE_PASSWORD: 'cypress',
},
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.js")(on, config);
}, },
baseUrl: "https://localhost:3000" 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.js')(on, config)
},
baseUrl: 'http://localhost:3000',
},
})

View File

@@ -1,19 +1,24 @@
/// <reference types="Cypress" /> /// <reference types="Cypress" />
const { FIREBASE_USERNAME, FIREBASE_PASSWORcD } = Cypress.env(); const {FIREBASE_USERNAME, FIREBASE_PASSWORcD} = Cypress.env();
describe("Renders the General Page", () => { describe("Renders the General Page", () => {
beforeEach(() => { beforeEach(() => {
cy.visit("/"); cy.visit("/");
}); });
it("Renders Correctly", () => {}); it("Renders Correctly", () => {
it("Has the Slogan", () => { });
cy.findByText("A whole x22new kind of shop management system.").should("exist"); it("Has the Slogan", () => {
/* ==== Generated with Cypress Studio ==== */ cy.findByText("A whole x22new kind of shop management system.").should(
cy.get(".ant-menu-item-active > .ant-menu-title-content > .header0-item-block").click(); "exist"
cy.get("#email").clear(); );
cy.get("#email").type("patrick@imex.dev"); /* ==== Generated with Cypress Studio ==== */
cy.get("#password").clear(); cy.get(
cy.get("#password").type("patrick123{enter}"); ".ant-menu-item-active > .ant-menu-title-content > .header0-item-block"
cy.get(".ant-form > .ant-btn").click(); ).click();
/* ==== End Cypress Studio ==== */ cy.get("#email").clear();
}); cy.get("#email").type("patrick@imex.dev");
cy.get("#password").clear();
cy.get("#password").type("patrick123{enter}");
cy.get(".ant-form > .ant-btn").click();
/* ==== End Cypress Studio ==== */
});
}); });

View File

@@ -11,114 +11,133 @@
// please read our getting started guide: // please read our getting started guide:
// https://on.cypress.io/introduction-to-cypress // https://on.cypress.io/introduction-to-cypress
describe("example to-do app", () => { 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 <label>, which is lowest-level element that contains the text.
// In order to check the item, we'll find the <input> element for this <label>
// by traversing up the dom to the parent element. From there, we can `find`
// the child checkbox <input> element and use the `check` command to check it.
cy.contains("Pay electric bill").parent().find("input[type=checkbox]").check();
// Now that we've checked the button, we can go ahead and make sure
// that the list element is now marked as completed.
// Again we'll use `contains` to find the <label> element and then use the `parents` command
// to traverse multiple levels up the dom until we find the corresponding <li> element.
// Once we get that element, we can assert that it has the completed class.
cy.contains("Pay electric bill").parents("li").should("have.class", "completed");
});
context("with a checked task", () => {
beforeEach(() => { beforeEach(() => {
// We'll take the command we used above to check off an element // Cypress starts out with a blank slate for each test
// Since we want to perform multiple tests that start with checking // so we must tell it to visit our website with the `cy.visit()` command.
// one element, we put it in the beforeEach hook // Since we want to visit the same URL at the start of all our tests,
// so that it runs at the start of every test. // we include it in our beforeEach function so that it runs before each test
cy.contains("Pay electric bill").parent().find("input[type=checkbox]").check(); cy.visit('https://example.cypress.io/todo')
}); })
it("can filter for uncompleted tasks", () => { it('displays two todo items by default', () => {
// We'll click on the "active" button in order to // We use the `cy.get()` command to get all elements that match the selector.
// display only incomplete items // Then, we use `should` to assert that there are two matched items,
cy.contains("Active").click(); // which are the two default items.
cy.get('.todo-list li').should('have.length', 2)
// After filtering, we can assert that there is only the one // We can go even further and check that the default todos each contain
// incomplete item in the list. // the correct text. We use the `first` and `last` functions
cy.get(".todo-list li").should("have.length", 1).first().should("have.text", "Walk the dog"); // 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')
})
// For good measure, let's also assert that the task we checked off it('can add new todo items', () => {
// does not exist on the page. // We'll store our item text in a variable so we can reuse it
cy.contains("Pay electric bill").should("not.exist"); const newItem = 'Feed the cat'
});
it("can filter for completed tasks", () => { // Let's get the input element and use the `type` command to
// We can perform similar steps as the test above to ensure // input our new list item. After typing the content of our item,
// that only completed tasks are shown // we need to type the enter key as well in order to submit the input.
cy.contains("Completed").click(); // 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}`)
cy.get(".todo-list li").should("have.length", 1).first().should("have.text", "Pay electric bill"); // 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)
})
cy.contains("Walk the dog").should("not.exist"); 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 <label>, which is lowest-level element that contains the text.
// In order to check the item, we'll find the <input> element for this <label>
// by traversing up the dom to the parent element. From there, we can `find`
// the child checkbox <input> element and use the `check` command to check it.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
it("can delete all completed tasks", () => { // Now that we've checked the button, we can go ahead and make sure
// First, let's click the "Clear completed" button // that the list element is now marked as completed.
// `contains` is actually serving two purposes here. // Again we'll use `contains` to find the <label> element and then use the `parents` command
// First, it's ensuring that the button exists within the dom. // to traverse multiple levels up the dom until we find the corresponding <li> element.
// This button only appears when at least one task is checked // Once we get that element, we can assert that it has the completed class.
// so this command is implicitly verifying that it does exist. cy.contains('Pay electric bill')
// Second, it selects the button so we can click it. .parents('li')
cy.contains("Clear completed").click(); .should('have.class', 'completed')
})
// Then we can make sure that there is only one element context('with a checked task', () => {
// in the list and our element does not exist beforeEach(() => {
cy.get(".todo-list li").should("have.length", 1).should("not.have.text", "Pay electric bill"); // We'll take the command we used above to check off an element
// Since we want to perform multiple tests that start with checking
// one element, we put it in the beforeEach hook
// so that it runs at the start of every test.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
})
// Finally, make sure that the clear button no longer exists. it('can filter for uncompleted tasks', () => {
cy.contains("Clear completed").should("not.exist"); // We'll click on the "active" button in order to
}); // display only incomplete items
}); cy.contains('Active').click()
});
// After filtering, we can assert that there is only the one
// incomplete item in the list.
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Walk the dog')
// For good measure, let's also assert that the task we checked off
// does not exist on the page.
cy.contains('Pay electric bill').should('not.exist')
})
it('can filter for completed tasks', () => {
// We can perform similar steps as the test above to ensure
// that only completed tasks are shown
cy.contains('Completed').click()
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Pay electric bill')
cy.contains('Walk the dog').should('not.exist')
})
it('can delete all completed tasks', () => {
// First, let's click the "Clear completed" button
// `contains` is actually serving two purposes here.
// First, it's ensuring that the button exists within the dom.
// This button only appears when at least one task is checked
// so this command is implicitly verifying that it does exist.
// Second, it selects the button so we can click it.
cy.contains('Clear completed').click()
// Then we can make sure that there is only one element
// in the list and our element does not exist
cy.get('.todo-list li')
.should('have.length', 1)
.should('not.have.text', 'Pay electric bill')
// Finally, make sure that the clear button no longer exists.
cy.contains('Clear completed').should('not.exist')
})
})
})

View File

@@ -1,284 +1,299 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Actions", () => { context('Actions', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/actions"); cy.visit('https://example.cypress.io/commands/actions')
}); })
// https://on.cypress.io/interacting-with-elements // https://on.cypress.io/interacting-with-elements
it(".type() - type into a DOM element", () => { it('.type() - type into a DOM element', () => {
// https://on.cypress.io/type // https://on.cypress.io/type
cy.get(".action-email") cy.get('.action-email')
.type("fake@email.com") .type('fake@email.com').should('have.value', 'fake@email.com')
.should("have.value", "fake@email.com")
// .type() with special character sequences // .type() with special character sequences
.type("{leftarrow}{rightarrow}{uparrow}{downarrow}") .type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
.type("{del}{selectall}{backspace}") .type('{del}{selectall}{backspace}')
// .type() with key modifiers // .type() with key modifiers
.type("{alt}{option}") //these are equivalent .type('{alt}{option}') //these are equivalent
.type("{ctrl}{control}") //these are equivalent .type('{ctrl}{control}') //these are equivalent
.type("{meta}{command}{cmd}") //these are equivalent .type('{meta}{command}{cmd}') //these are equivalent
.type("{shift}") .type('{shift}')
// Delay each keypress by 0.1 sec // Delay each keypress by 0.1 sec
.type("slow.typing@email.com", { delay: 100 }) .type('slow.typing@email.com', {delay: 100})
.should("have.value", "slow.typing@email.com"); .should('have.value', 'slow.typing@email.com')
cy.get(".action-disabled") cy.get('.action-disabled')
// Ignore error checking prior to type // Ignore error checking prior to type
// like whether the input is visible or disabled // like whether the input is visible or disabled
.type("disabled error checking", { force: true }) .type('disabled error checking', {force: true})
.should("have.value", "disabled error checking"); .should('have.value', 'disabled error checking')
}); })
it(".focus() - focus on a DOM element", () => { it('.focus() - focus on a DOM element', () => {
// https://on.cypress.io/focus // https://on.cypress.io/focus
cy.get(".action-focus").focus().should("have.class", "focus").prev().should("have.attr", "style", "color: orange;"); cy.get('.action-focus').focus()
}); .should('have.class', 'focus')
.prev().should('have.attr', 'style', 'color: orange;')
})
it(".blur() - blur off a DOM element", () => { it('.blur() - blur off a DOM element', () => {
// https://on.cypress.io/blur // https://on.cypress.io/blur
cy.get(".action-blur") cy.get('.action-blur').type('About to blur').blur()
.type("About to blur") .should('have.class', 'error')
.blur() .prev().should('have.attr', 'style', 'color: red;')
.should("have.class", "error") })
.prev()
.should("have.attr", "style", "color: red;");
});
it(".clear() - clears an input or textarea element", () => { it('.clear() - clears an input or textarea element', () => {
// https://on.cypress.io/clear // https://on.cypress.io/clear
cy.get(".action-clear") cy.get('.action-clear').type('Clear this text')
.type("Clear this text") .should('have.value', 'Clear this text')
.should("have.value", "Clear this text") .clear()
.clear() .should('have.value', '')
.should("have.value", ""); })
});
it(".submit() - submit a form", () => { it('.submit() - submit a form', () => {
// https://on.cypress.io/submit // https://on.cypress.io/submit
cy.get(".action-form").find('[type="text"]').type("HALFOFF"); cy.get('.action-form')
.find('[type="text"]').type('HALFOFF')
cy.get(".action-form").submit().next().should("contain", "Your form has been submitted!"); cy.get('.action-form').submit()
}); .next().should('contain', 'Your form has been submitted!')
})
it(".click() - click on a DOM element", () => { it('.click() - click on a DOM element', () => {
// https://on.cypress.io/click // https://on.cypress.io/click
cy.get(".action-btn").click(); cy.get('.action-btn').click()
// You can click on 9 specific positions of an element: // You can click on 9 specific positions of an element:
// ----------------------------------- // -----------------------------------
// | topLeft top topRight | // | topLeft top topRight |
// | | // | |
// | | // | |
// | | // | |
// | left center right | // | left center right |
// | | // | |
// | | // | |
// | | // | |
// | bottomLeft bottom bottomRight | // | bottomLeft bottom bottomRight |
// ----------------------------------- // -----------------------------------
// clicking in the center of the element is the default // clicking in the center of the element is the default
cy.get("#action-canvas").click(); cy.get('#action-canvas').click()
cy.get("#action-canvas").click("topLeft"); cy.get('#action-canvas').click('topLeft')
cy.get("#action-canvas").click("top"); cy.get('#action-canvas').click('top')
cy.get("#action-canvas").click("topRight"); cy.get('#action-canvas').click('topRight')
cy.get("#action-canvas").click("left"); cy.get('#action-canvas').click('left')
cy.get("#action-canvas").click("right"); cy.get('#action-canvas').click('right')
cy.get("#action-canvas").click("bottomLeft"); cy.get('#action-canvas').click('bottomLeft')
cy.get("#action-canvas").click("bottom"); cy.get('#action-canvas').click('bottom')
cy.get("#action-canvas").click("bottomRight"); cy.get('#action-canvas').click('bottomRight')
// .click() accepts an x and y coordinate // .click() accepts an x and y coordinate
// that controls where the click occurs :) // that controls where the click occurs :)
cy.get("#action-canvas") cy.get('#action-canvas')
.click(80, 75) // click 80px on x coord and 75px on y coord .click(80, 75) // click 80px on x coord and 75px on y coord
.click(170, 75) .click(170, 75)
.click(80, 165) .click(80, 165)
.click(100, 185) .click(100, 185)
.click(125, 190) .click(125, 190)
.click(150, 185) .click(150, 185)
.click(170, 165); .click(170, 165)
// click multiple elements by passing multiple: true // click multiple elements by passing multiple: true
cy.get(".action-labels>.label").click({ multiple: true }); cy.get('.action-labels>.label').click({multiple: true})
// Ignore error checking prior to clicking // Ignore error checking prior to clicking
cy.get(".action-opacity>.btn").click({ force: true }); cy.get('.action-opacity>.btn').click({force: true})
}); })
it(".dblclick() - double click on a DOM element", () => { it('.dblclick() - double click on a DOM element', () => {
// https://on.cypress.io/dblclick // https://on.cypress.io/dblclick
// Our app has a listener on 'dblclick' event in our 'scripts.js' // Our app has a listener on 'dblclick' event in our 'scripts.js'
// that hides the div and shows an input on double click // that hides the div and shows an input on double click
cy.get(".action-div").dblclick().should("not.be.visible"); cy.get('.action-div').dblclick().should('not.be.visible')
cy.get(".action-input-hidden").should("be.visible"); cy.get('.action-input-hidden').should('be.visible')
}); })
it(".rightclick() - right click on a DOM element", () => { it('.rightclick() - right click on a DOM element', () => {
// https://on.cypress.io/rightclick // https://on.cypress.io/rightclick
// Our app has a listener on 'contextmenu' event in our 'scripts.js' // Our app has a listener on 'contextmenu' event in our 'scripts.js'
// that hides the div and shows an input on right click // that hides the div and shows an input on right click
cy.get(".rightclick-action-div").rightclick().should("not.be.visible"); cy.get('.rightclick-action-div').rightclick().should('not.be.visible')
cy.get(".rightclick-action-input-hidden").should("be.visible"); cy.get('.rightclick-action-input-hidden').should('be.visible')
}); })
it(".check() - check a checkbox or radio element", () => { it('.check() - check a checkbox or radio element', () => {
// https://on.cypress.io/check // https://on.cypress.io/check
// By default, .check() will check all // By default, .check() will check all
// matching checkbox or radio elements in succession, one after another // matching checkbox or radio elements in succession, one after another
cy.get('.action-checkboxes [type="checkbox"]').not("[disabled]").check().should("be.checked"); cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
.check().should('be.checked')
cy.get('.action-radios [type="radio"]').not("[disabled]").check().should("be.checked"); cy.get('.action-radios [type="radio"]').not('[disabled]')
.check().should('be.checked')
// .check() accepts a value argument // .check() accepts a value argument
cy.get('.action-radios [type="radio"]').check("radio1").should("be.checked"); cy.get('.action-radios [type="radio"]')
.check('radio1').should('be.checked')
// .check() accepts an array of values // .check() accepts an array of values
cy.get('.action-multiple-checkboxes [type="checkbox"]').check(["checkbox1", "checkbox2"]).should("be.checked"); cy.get('.action-multiple-checkboxes [type="checkbox"]')
.check(['checkbox1', 'checkbox2']).should('be.checked')
// Ignore error checking prior to checking // Ignore error checking prior to checking
cy.get(".action-checkboxes [disabled]").check({ force: true }).should("be.checked"); cy.get('.action-checkboxes [disabled]')
.check({force: true}).should('be.checked')
cy.get('.action-radios [type="radio"]').check("radio3", { force: true }).should("be.checked"); cy.get('.action-radios [type="radio"]')
}); .check('radio3', {force: true}).should('be.checked')
})
it(".uncheck() - uncheck a checkbox element", () => { it('.uncheck() - uncheck a checkbox element', () => {
// https://on.cypress.io/uncheck // https://on.cypress.io/uncheck
// By default, .uncheck() will uncheck all matching // By default, .uncheck() will uncheck all matching
// checkbox elements in succession, one after another // checkbox elements in succession, one after another
cy.get('.action-check [type="checkbox"]').not("[disabled]").uncheck().should("not.be.checked"); cy.get('.action-check [type="checkbox"]')
.not('[disabled]')
.uncheck().should('not.be.checked')
// .uncheck() accepts a value argument // .uncheck() accepts a value argument
cy.get('.action-check [type="checkbox"]').check("checkbox1").uncheck("checkbox1").should("not.be.checked"); cy.get('.action-check [type="checkbox"]')
.check('checkbox1')
.uncheck('checkbox1').should('not.be.checked')
// .uncheck() accepts an array of values // .uncheck() accepts an array of values
cy.get('.action-check [type="checkbox"]') cy.get('.action-check [type="checkbox"]')
.check(["checkbox1", "checkbox3"]) .check(['checkbox1', 'checkbox3'])
.uncheck(["checkbox1", "checkbox3"]) .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
.should("not.be.checked");
// Ignore error checking prior to unchecking // Ignore error checking prior to unchecking
cy.get(".action-check [disabled]").uncheck({ force: true }).should("not.be.checked"); cy.get('.action-check [disabled]')
}); .uncheck({force: true}).should('not.be.checked')
})
it(".select() - select an option in a <select> element", () => { it('.select() - select an option in a <select> element', () => {
// https://on.cypress.io/select // https://on.cypress.io/select
// at first, no option should be selected // at first, no option should be selected
cy.get(".action-select").should("have.value", "--Select a fruit--"); cy.get('.action-select')
.should('have.value', '--Select a fruit--')
// Select option(s) with matching text content // Select option(s) with matching text content
cy.get(".action-select").select("apples"); cy.get('.action-select').select('apples')
// confirm the apples were selected // confirm the apples were selected
// note that each value starts with "fr-" in our HTML // note that each value starts with "fr-" in our HTML
cy.get(".action-select").should("have.value", "fr-apples"); cy.get('.action-select').should('have.value', 'fr-apples')
cy.get(".action-select-multiple") cy.get('.action-select-multiple')
.select(["apples", "oranges", "bananas"]) .select(['apples', 'oranges', 'bananas'])
// when getting multiple values, invoke "val" method first // when getting multiple values, invoke "val" method first
.invoke("val") .invoke('val')
.should("deep.equal", ["fr-apples", "fr-oranges", "fr-bananas"]); .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
// Select option(s) with matching value // Select option(s) with matching value
cy.get(".action-select") cy.get('.action-select').select('fr-bananas')
.select("fr-bananas") // can attach an assertion right away to the element
// can attach an assertion right away to the element .should('have.value', 'fr-bananas')
.should("have.value", "fr-bananas");
cy.get(".action-select-multiple") cy.get('.action-select-multiple')
.select(["fr-apples", "fr-oranges", "fr-bananas"]) .select(['fr-apples', 'fr-oranges', 'fr-bananas'])
.invoke("val") .invoke('val')
.should("deep.equal", ["fr-apples", "fr-oranges", "fr-bananas"]); .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
// assert the selected values include oranges // assert the selected values include oranges
cy.get(".action-select-multiple").invoke("val").should("include", "fr-oranges"); cy.get('.action-select-multiple')
}); .invoke('val').should('include', 'fr-oranges')
})
it(".scrollIntoView() - scroll an element into view", () => { it('.scrollIntoView() - scroll an element into view', () => {
// https://on.cypress.io/scrollintoview // https://on.cypress.io/scrollintoview
// normally all of these buttons are hidden, // normally all of these buttons are hidden,
// because they're not within // because they're not within
// the viewable area of their parent // the viewable area of their parent
// (we need to scroll to see them) // (we need to scroll to see them)
cy.get("#scroll-horizontal button").should("not.be.visible"); cy.get('#scroll-horizontal button')
.should('not.be.visible')
// scroll the button into view, as if the user had scrolled // scroll the button into view, as if the user had scrolled
cy.get("#scroll-horizontal button").scrollIntoView().should("be.visible"); cy.get('#scroll-horizontal button').scrollIntoView()
.should('be.visible')
cy.get("#scroll-vertical button").should("not.be.visible"); cy.get('#scroll-vertical button')
.should('not.be.visible')
// Cypress handles the scroll direction needed // Cypress handles the scroll direction needed
cy.get("#scroll-vertical button").scrollIntoView().should("be.visible"); cy.get('#scroll-vertical button').scrollIntoView()
.should('be.visible')
cy.get("#scroll-both button").should("not.be.visible"); cy.get('#scroll-both button')
.should('not.be.visible')
// Cypress knows to scroll to the right and down // Cypress knows to scroll to the right and down
cy.get("#scroll-both button").scrollIntoView().should("be.visible"); cy.get('#scroll-both button').scrollIntoView()
}); .should('be.visible')
})
it(".trigger() - trigger an event on a DOM element", () => { it('.trigger() - trigger an event on a DOM element', () => {
// https://on.cypress.io/trigger // https://on.cypress.io/trigger
// To interact with a range input (slider) // To interact with a range input (slider)
// we need to set its value & trigger the // we need to set its value & trigger the
// event to signal it changed // event to signal it changed
// Here, we invoke jQuery's val() method to set // Here, we invoke jQuery's val() method to set
// the value and trigger the 'change' event // the value and trigger the 'change' event
cy.get(".trigger-input-range") cy.get('.trigger-input-range')
.invoke("val", 25) .invoke('val', 25)
.trigger("change") .trigger('change')
.get("input[type=range]") .get('input[type=range]').siblings('p')
.siblings("p") .should('have.text', '25')
.should("have.text", "25"); })
});
it("cy.scrollTo() - scroll the window or element to a position", () => { it('cy.scrollTo() - scroll the window or element to a position', () => {
// https://on.cypress.io/scrollto // https://on.cypress.io/scrollto
// You can scroll to 9 specific positions of an element: // You can scroll to 9 specific positions of an element:
// ----------------------------------- // -----------------------------------
// | topLeft top topRight | // | topLeft top topRight |
// | | // | |
// | | // | |
// | | // | |
// | left center right | // | left center right |
// | | // | |
// | | // | |
// | | // | |
// | bottomLeft bottom bottomRight | // | bottomLeft bottom bottomRight |
// ----------------------------------- // -----------------------------------
// if you chain .scrollTo() off of cy, we will // if you chain .scrollTo() off of cy, we will
// scroll the entire window // scroll the entire window
cy.scrollTo("bottom"); cy.scrollTo('bottom')
cy.get("#scrollable-horizontal").scrollTo("right"); cy.get('#scrollable-horizontal').scrollTo('right')
// or you can scroll to a specific coordinate: // or you can scroll to a specific coordinate:
// (x axis, y axis) in pixels // (x axis, y axis) in pixels
cy.get("#scrollable-vertical").scrollTo(250, 250); cy.get('#scrollable-vertical').scrollTo(250, 250)
// or you can scroll to a specific percentage // or you can scroll to a specific percentage
// of the (width, height) of the element // of the (width, height) of the element
cy.get("#scrollable-both").scrollTo("75%", "25%"); cy.get('#scrollable-both').scrollTo('75%', '25%')
// control the easing of the scroll (default is 'swing') // control the easing of the scroll (default is 'swing')
cy.get("#scrollable-vertical").scrollTo("center", { easing: "linear" }); cy.get('#scrollable-vertical').scrollTo('center', {easing: 'linear'})
// control the duration of the scroll (in ms) // control the duration of the scroll (in ms)
cy.get("#scrollable-both").scrollTo("center", { duration: 2000 }); cy.get('#scrollable-both').scrollTo('center', {duration: 2000})
}); })
}); })

View File

@@ -1,35 +1,39 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Aliasing", () => { context('Aliasing', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/aliasing"); cy.visit('https://example.cypress.io/commands/aliasing')
}); })
it(".as() - alias a DOM element for later use", () => { it('.as() - alias a DOM element for later use', () => {
// https://on.cypress.io/as // https://on.cypress.io/as
// Alias a DOM element for use later // Alias a DOM element for use later
// We don't have to traverse to the element // We don't have to traverse to the element
// later in our code, we reference it with @ // later in our code, we reference it with @
cy.get(".as-table").find("tbody>tr").first().find("td").first().find("button").as("firstBtn"); cy.get('.as-table').find('tbody>tr')
.first().find('td').first()
.find('button').as('firstBtn')
// when we reference the alias, we place an // when we reference the alias, we place an
// @ in front of its name // @ in front of its name
cy.get("@firstBtn").click(); cy.get('@firstBtn').click()
cy.get("@firstBtn").should("have.class", "btn-success").and("contain", "Changed"); cy.get('@firstBtn')
}); .should('have.class', 'btn-success')
.and('contain', 'Changed')
})
it(".as() - alias a route for later use", () => { it('.as() - alias a route for later use', () => {
// Alias the route to wait for its response // Alias the route to wait for its response
cy.intercept("GET", "**/comments/*").as("getComment"); cy.intercept('GET', '**/comments/*').as('getComment')
// we have code that gets a comment when // we have code that gets a comment when
// the button is clicked in scripts.js // the button is clicked in scripts.js
cy.get(".network-btn").click(); cy.get('.network-btn').click()
// https://on.cypress.io/wait // https://on.cypress.io/wait
cy.wait("@getComment").its("response.statusCode").should("eq", 200); cy.wait('@getComment').its('response.statusCode').should('eq', 200)
}); })
}); })

View File

@@ -1,173 +1,177 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Assertions", () => { context('Assertions', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/assertions"); cy.visit('https://example.cypress.io/commands/assertions')
}); })
describe("Implicit Assertions", () => { describe('Implicit Assertions', () => {
it(".should() - make an assertion about the current subject", () => { it('.should() - make an assertion about the current subject', () => {
// https://on.cypress.io/should // https://on.cypress.io/should
cy.get(".assertion-table") cy.get('.assertion-table')
.find("tbody tr:last") .find('tbody tr:last')
.should("have.class", "success") .should('have.class', 'success')
.find("td") .find('td')
.first() .first()
// checking the text of the <td> element in various ways // checking the text of the <td> element in various ways
.should("have.text", "Column content") .should('have.text', 'Column content')
.should("contain", "Column content") .should('contain', 'Column content')
.should("have.html", "Column content") .should('have.html', 'Column content')
// chai-jquery uses "is()" to check if element matches selector // chai-jquery uses "is()" to check if element matches selector
.should("match", "td") .should('match', 'td')
// to match text content against a regular expression // to match text content against a regular expression
// first need to invoke jQuery method text() // first need to invoke jQuery method text()
// and then match using regular expression // and then match using regular expression
.invoke("text") .invoke('text')
.should("match", /column content/i); .should('match', /column content/i)
// a better way to check element's text content against a regular expression // a better way to check element's text content against a regular expression
// is to use "cy.contains" // is to use "cy.contains"
// https://on.cypress.io/contains // https://on.cypress.io/contains
cy.get(".assertion-table") cy.get('.assertion-table')
.find("tbody tr:last") .find('tbody tr:last')
// finds first <td> element with text content matching regular expression // finds first <td> element with text content matching regular expression
.contains("td", /column content/i) .contains('td', /column content/i)
.should("be.visible"); .should('be.visible')
// for more information about asserting element's text // for more information about asserting element's text
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-elements-text-contents // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-elements-text-contents
});
it(".and() - chain multiple assertions together", () => {
// https://on.cypress.io/and
cy.get(".assertions-link").should("have.class", "active").and("have.attr", "href").and("include", "cypress.io");
});
});
describe("Explicit Assertions", () => {
// https://on.cypress.io/assertions
it("expect - make an assertion about a specified subject", () => {
// We can use Chai's BDD style assertions
expect(true).to.be.true;
const o = { foo: "bar" };
expect(o).to.equal(o);
expect(o).to.deep.equal({ foo: "bar" });
// matching text using regular expression
expect("FooBar").to.match(/bar$/i);
});
it("pass your own callback function to should()", () => {
// Pass a function to should that can have any number
// of explicit assertions within it.
// The ".should(cb)" function will be retried
// automatically until it passes all your explicit assertions or times out.
cy.get(".assertions-p")
.find("p")
.should(($p) => {
// https://on.cypress.io/$
// return an array of texts from all of the p's
// @ts-ignore TS6133 unused variable
const texts = $p.map((i, el) => Cypress.$(el).text());
// jquery map returns jquery object
// and .get() convert this to simple array
const paragraphs = texts.get();
// array should have length of 3
expect(paragraphs, "has 3 paragraphs").to.have.length(3);
// use second argument to expect(...) to provide clear
// message with each assertion
expect(paragraphs, "has expected text in each paragraph").to.deep.eq([
"Some text from first p",
"More text from second p",
"And even more text from third p"
]);
});
});
it("finds element by class name regex", () => {
cy.get(".docs-header")
.find("div")
// .should(cb) callback function will be retried
.should(($div) => {
expect($div).to.have.length(1);
const className = $div[0].className;
expect(className).to.match(/heading-/);
}) })
// .then(cb) callback is not retried,
// it either passes or fails
.then(($div) => {
expect($div, "text content").to.have.text("Introduction");
});
});
it("can throw any error", () => { it('.and() - chain multiple assertions together', () => {
cy.get(".docs-header") // https://on.cypress.io/and
.find("div") cy.get('.assertions-link')
.should(($div) => { .should('have.class', 'active')
if ($div.length !== 1) { .and('have.attr', 'href')
// you can throw your own errors .and('include', 'cypress.io')
throw new Error("Did not find 1 element"); })
} })
const className = $div[0].className; describe('Explicit Assertions', () => {
// https://on.cypress.io/assertions
it('expect - make an assertion about a specified subject', () => {
// We can use Chai's BDD style assertions
expect(true).to.be.true
const o = {foo: 'bar'}
if (!className.match(/heading-/)) { expect(o).to.equal(o)
throw new Error(`Could not find class "heading-" in ${className}`); expect(o).to.deep.equal({foo: 'bar'})
} // matching text using regular expression
}); expect('FooBar').to.match(/bar$/i)
}); })
it("matches unknown text between two elements", () => { it('pass your own callback function to should()', () => {
/** // Pass a function to should that can have any number
* Text from the first element. // of explicit assertions within it.
* @type {string} // The ".should(cb)" function will be retried
*/ // automatically until it passes all your explicit assertions or times out.
let text; cy.get('.assertions-p')
.find('p')
.should(($p) => {
// https://on.cypress.io/$
// return an array of texts from all of the p's
// @ts-ignore TS6133 unused variable
const texts = $p.map((i, el) => Cypress.$(el).text())
/** // jquery map returns jquery object
* Normalizes passed text, // and .get() convert this to simple array
* useful before comparing text with spaces and different capitalization. const paragraphs = texts.get()
* @param {string} s Text to normalize
*/
const normalizeText = (s) => s.replace(/\s/g, "").toLowerCase();
cy.get(".two-elements") // array should have length of 3
.find(".first") expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
.then(($first) => {
// save text from the first element
text = normalizeText($first.text());
});
cy.get(".two-elements") // use second argument to expect(...) to provide clear
.find(".second") // message with each assertion
.should(($div) => { expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
// we can massage text before comparing 'Some text from first p',
const secondText = normalizeText($div.text()); 'More text from second p',
'And even more text from third p',
])
})
})
expect(secondText, "second text").to.equal(text); it('finds element by class name regex', () => {
}); cy.get('.docs-header')
}); .find('div')
// .should(cb) callback function will be retried
.should(($div) => {
expect($div).to.have.length(1)
it("assert - assert shape of an object", () => { const className = $div[0].className
const person = {
name: "Joe",
age: 20
};
assert.isObject(person, "value is object"); expect(className).to.match(/heading-/)
}); })
// .then(cb) callback is not retried,
// it either passes or fails
.then(($div) => {
expect($div, 'text content').to.have.text('Introduction')
})
})
it("retries the should callback until assertions pass", () => { it('can throw any error', () => {
cy.get("#random-number").should(($div) => { cy.get('.docs-header')
const n = parseFloat($div.text()); .find('div')
.should(($div) => {
if ($div.length !== 1) {
// you can throw your own errors
throw new Error('Did not find 1 element')
}
expect(n).to.be.gte(1).and.be.lte(10); const className = $div[0].className
});
}); if (!className.match(/heading-/)) {
}); throw new Error(`Could not find class "heading-" in ${className}`)
}); }
})
})
it('matches unknown text between two elements', () => {
/**
* Text from the first element.
* @type {string}
*/
let text
/**
* Normalizes passed text,
* useful before comparing text with spaces and different capitalization.
* @param {string} s Text to normalize
*/
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
cy.get('.two-elements')
.find('.first')
.then(($first) => {
// save text from the first element
text = normalizeText($first.text())
})
cy.get('.two-elements')
.find('.second')
.should(($div) => {
// we can massage text before comparing
const secondText = normalizeText($div.text())
expect(secondText, 'second text').to.equal(text)
})
})
it('assert - assert shape of an object', () => {
const person = {
name: 'Joe',
age: 20,
}
assert.isObject(person, 'value is object')
})
it('retries the should callback until assertions pass', () => {
cy.get('#random-number')
.should(($div) => {
const n = parseFloat($div.text())
expect(n).to.be.gte(1).and.be.lte(10)
})
})
})
})

View File

@@ -1,96 +1,97 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Connectors", () => { context('Connectors', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/connectors"); cy.visit('https://example.cypress.io/commands/connectors')
}); })
it(".each() - iterate over an array of elements", () => { it('.each() - iterate over an array of elements', () => {
// https://on.cypress.io/each // https://on.cypress.io/each
cy.get(".connectors-each-ul>li").each(($el, index, $list) => { cy.get('.connectors-each-ul>li')
console.log($el, index, $list); .each(($el, index, $list) => {
}); console.log($el, index, $list)
}); })
})
it(".its() - get properties on the current subject", () => { it('.its() - get properties on the current subject', () => {
// https://on.cypress.io/its // https://on.cypress.io/its
cy.get(".connectors-its-ul>li") cy.get('.connectors-its-ul>li')
// calls the 'length' property yielding that value // calls the 'length' property yielding that value
.its("length") .its('length')
.should("be.gt", 2); .should('be.gt', 2)
}); })
it(".invoke() - invoke a function on the current subject", () => { it('.invoke() - invoke a function on the current subject', () => {
// our div is hidden in our script.js // our div is hidden in our script.js
// $('.connectors-div').hide() // $('.connectors-div').hide()
// https://on.cypress.io/invoke // https://on.cypress.io/invoke
cy.get(".connectors-div") cy.get('.connectors-div').should('be.hidden')
.should("be.hidden") // call the jquery method 'show' on the 'div.container'
// call the jquery method 'show' on the 'div.container' .invoke('show')
.invoke("show") .should('be.visible')
.should("be.visible"); })
});
it(".spread() - spread an array as individual args to callback function", () => { it('.spread() - spread an array as individual args to callback function', () => {
// https://on.cypress.io/spread // https://on.cypress.io/spread
const arr = ["foo", "bar", "baz"]; const arr = ['foo', 'bar', 'baz']
cy.wrap(arr).spread((foo, bar, baz) => { cy.wrap(arr).spread((foo, bar, baz) => {
expect(foo).to.eq("foo"); expect(foo).to.eq('foo')
expect(bar).to.eq("bar"); expect(bar).to.eq('bar')
expect(baz).to.eq("baz"); expect(baz).to.eq('baz')
});
});
describe(".then()", () => {
it("invokes a callback function with the current subject", () => {
// https://on.cypress.io/then
cy.get(".connectors-list > li").then(($lis) => {
expect($lis, "3 items").to.have.length(3);
expect($lis.eq(0), "first item").to.contain("Walk the dog");
expect($lis.eq(1), "second item").to.contain("Feed the cat");
expect($lis.eq(2), "third item").to.contain("Write JavaScript");
});
});
it("yields the returned value to the next command", () => {
cy.wrap(1)
.then((num) => {
expect(num).to.equal(1);
return 2;
}) })
.then((num) => { })
expect(num).to.equal(2);
});
});
it("yields the original subject without return", () => { describe('.then()', () => {
cy.wrap(1) it('invokes a callback function with the current subject', () => {
.then((num) => { // https://on.cypress.io/then
expect(num).to.equal(1); cy.get('.connectors-list > li')
// note that nothing is returned from this callback .then(($lis) => {
expect($lis, '3 items').to.have.length(3)
expect($lis.eq(0), 'first item').to.contain('Walk the dog')
expect($lis.eq(1), 'second item').to.contain('Feed the cat')
expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
})
}) })
.then((num) => {
// this callback receives the original unchanged value 1
expect(num).to.equal(1);
});
});
it("yields the value yielded by the last Cypress command inside", () => { it('yields the returned value to the next command', () => {
cy.wrap(1) cy.wrap(1)
.then((num) => { .then((num) => {
expect(num).to.equal(1); expect(num).to.equal(1)
// note how we run a Cypress command
// the result yielded by this Cypress command return 2
// will be passed to the second ".then" })
cy.wrap(2); .then((num) => {
expect(num).to.equal(2)
})
}) })
.then((num) => {
// this callback receives the value yielded by "cy.wrap(2)" it('yields the original subject without return', () => {
expect(num).to.equal(2); cy.wrap(1)
}); .then((num) => {
}); expect(num).to.equal(1)
}); // note that nothing is returned from this callback
}); })
.then((num) => {
// this callback receives the original unchanged value 1
expect(num).to.equal(1)
})
})
it('yields the value yielded by the last Cypress command inside', () => {
cy.wrap(1)
.then((num) => {
expect(num).to.equal(1)
// note how we run a Cypress command
// the result yielded by this Cypress command
// will be passed to the second ".then"
cy.wrap(2)
})
.then((num) => {
// this callback receives the value yielded by "cy.wrap(2)"
expect(num).to.equal(2)
})
})
})
})

View File

@@ -1,79 +1,77 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Cookies", () => { context('Cookies', () => {
beforeEach(() => { beforeEach(() => {
Cypress.Cookies.debug(true); Cypress.Cookies.debug(true)
cy.visit("https://example.cypress.io/commands/cookies"); cy.visit('https://example.cypress.io/commands/cookies')
// clear cookies again after visiting to remove // clear cookies again after visiting to remove
// any 3rd party cookies picked up such as cloudflare // any 3rd party cookies picked up such as cloudflare
cy.clearCookies(); cy.clearCookies()
}); })
it("cy.getCookie() - get a browser cookie", () => { it('cy.getCookie() - get a browser cookie', () => {
// https://on.cypress.io/getcookie // https://on.cypress.io/getcookie
cy.get("#getCookie .set-a-cookie").click(); cy.get('#getCookie .set-a-cookie').click()
// cy.getCookie() yields a cookie object // cy.getCookie() yields a cookie object
cy.getCookie("token").should("have.property", "value", "123ABC"); cy.getCookie('token').should('have.property', 'value', '123ABC')
}); })
it("cy.getCookies() - get browser cookies", () => { it('cy.getCookies() - get browser cookies', () => {
// https://on.cypress.io/getcookies // https://on.cypress.io/getcookies
cy.getCookies().should("be.empty"); cy.getCookies().should('be.empty')
cy.get("#getCookies .set-a-cookie").click(); cy.get('#getCookies .set-a-cookie').click()
// cy.getCookies() yields an array of cookies // cy.getCookies() yields an array of cookies
cy.getCookies() cy.getCookies().should('have.length', 1).should((cookies) => {
.should("have.length", 1) // each cookie has these properties
.should((cookies) => { expect(cookies[0]).to.have.property('name', 'token')
// each cookie has these properties expect(cookies[0]).to.have.property('value', '123ABC')
expect(cookies[0]).to.have.property("name", "token"); expect(cookies[0]).to.have.property('httpOnly', false)
expect(cookies[0]).to.have.property("value", "123ABC"); expect(cookies[0]).to.have.property('secure', false)
expect(cookies[0]).to.have.property("httpOnly", false); expect(cookies[0]).to.have.property('domain')
expect(cookies[0]).to.have.property("secure", false); expect(cookies[0]).to.have.property('path')
expect(cookies[0]).to.have.property("domain"); })
expect(cookies[0]).to.have.property("path"); })
});
});
it("cy.setCookie() - set a browser cookie", () => { it('cy.setCookie() - set a browser cookie', () => {
// https://on.cypress.io/setcookie // https://on.cypress.io/setcookie
cy.getCookies().should("be.empty"); cy.getCookies().should('be.empty')
cy.setCookie("foo", "bar"); cy.setCookie('foo', 'bar')
// cy.getCookie() yields a cookie object // cy.getCookie() yields a cookie object
cy.getCookie("foo").should("have.property", "value", "bar"); cy.getCookie('foo').should('have.property', 'value', 'bar')
}); })
it("cy.clearCookie() - clear a browser cookie", () => { it('cy.clearCookie() - clear a browser cookie', () => {
// https://on.cypress.io/clearcookie // https://on.cypress.io/clearcookie
cy.getCookie("token").should("be.null"); cy.getCookie('token').should('be.null')
cy.get("#clearCookie .set-a-cookie").click(); cy.get('#clearCookie .set-a-cookie').click()
cy.getCookie("token").should("have.property", "value", "123ABC"); cy.getCookie('token').should('have.property', 'value', '123ABC')
// cy.clearCookies() yields null // cy.clearCookies() yields null
cy.clearCookie("token").should("be.null"); cy.clearCookie('token').should('be.null')
cy.getCookie("token").should("be.null"); cy.getCookie('token').should('be.null')
}); })
it("cy.clearCookies() - clear browser cookies", () => { it('cy.clearCookies() - clear browser cookies', () => {
// https://on.cypress.io/clearcookies // https://on.cypress.io/clearcookies
cy.getCookies().should("be.empty"); cy.getCookies().should('be.empty')
cy.get("#clearCookies .set-a-cookie").click(); cy.get('#clearCookies .set-a-cookie').click()
cy.getCookies().should("have.length", 1); cy.getCookies().should('have.length', 1)
// cy.clearCookies() yields null // cy.clearCookies() yields null
cy.clearCookies(); cy.clearCookies()
cy.getCookies().should("be.empty"); cy.getCookies().should('be.empty')
}); })
}); })

View File

@@ -1,208 +1,202 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Cypress.Commands", () => { context('Cypress.Commands', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
// https://on.cypress.io/custom-commands // https://on.cypress.io/custom-commands
it(".add() - create a custom command", () => { it('.add() - create a custom command', () => {
Cypress.Commands.add( Cypress.Commands.add('console', {
"console", prevSubject: true,
{ }, (subject, method) => {
prevSubject: true // the previous subject is automatically received
}, // and the commands arguments are shifted
(subject, method) => {
// the previous subject is automatically received
// and the commands arguments are shifted
// allow us to change the console method used // allow us to change the console method used
method = method || "log"; method = method || 'log'
// log the subject to the console // log the subject to the console
// @ts-ignore TS7017 // @ts-ignore TS7017
console[method]("The subject is", subject); console[method]('The subject is', subject)
// whatever we return becomes the new subject // whatever we return becomes the new subject
// we don't want to change the subject so // we don't want to change the subject so
// we return whatever was passed in // we return whatever was passed in
return subject; return subject
} })
);
// @ts-ignore TS2339 // @ts-ignore TS2339
cy.get("button") cy.get('button').console('info').then(($button) => {
.console("info") // subject is still $button
.then(($button) => { })
// subject is still $button })
}); })
});
});
context("Cypress.Cookies", () => { context('Cypress.Cookies', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
// https://on.cypress.io/cookies // https://on.cypress.io/cookies
it(".debug() - enable or disable debugging", () => { it('.debug() - enable or disable debugging', () => {
Cypress.Cookies.debug(true); Cypress.Cookies.debug(true)
// Cypress will now log in the console when // Cypress will now log in the console when
// cookies are set or cleared // cookies are set or cleared
cy.setCookie("fakeCookie", "123ABC"); cy.setCookie('fakeCookie', '123ABC')
cy.clearCookie("fakeCookie"); cy.clearCookie('fakeCookie')
cy.setCookie("fakeCookie", "123ABC"); cy.setCookie('fakeCookie', '123ABC')
cy.clearCookie("fakeCookie"); cy.clearCookie('fakeCookie')
cy.setCookie("fakeCookie", "123ABC"); cy.setCookie('fakeCookie', '123ABC')
}); })
it(".preserveOnce() - preserve cookies by key", () => { it('.preserveOnce() - preserve cookies by key', () => {
// normally cookies are reset after each test // normally cookies are reset after each test
cy.getCookie("fakeCookie").should("not.be.ok"); cy.getCookie('fakeCookie').should('not.be.ok')
// preserving a cookie will not clear it when // preserving a cookie will not clear it when
// the next test starts // the next test starts
cy.setCookie("lastCookie", "789XYZ"); cy.setCookie('lastCookie', '789XYZ')
Cypress.Cookies.preserveOnce("lastCookie"); Cypress.Cookies.preserveOnce('lastCookie')
}); })
it(".defaults() - set defaults for all cookies", () => { it('.defaults() - set defaults for all cookies', () => {
// now any cookie with the name 'session_id' will // now any cookie with the name 'session_id' will
// not be cleared before each new test runs // not be cleared before each new test runs
Cypress.Cookies.defaults({ Cypress.Cookies.defaults({
preserve: "session_id" preserve: 'session_id',
}); })
}); })
}); })
context("Cypress.arch", () => { context('Cypress.arch', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
it("Get CPU architecture name of underlying OS", () => { it('Get CPU architecture name of underlying OS', () => {
// https://on.cypress.io/arch // https://on.cypress.io/arch
expect(Cypress.arch).to.exist; expect(Cypress.arch).to.exist
}); })
}); })
context("Cypress.config()", () => { context('Cypress.config()', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
it("Get and set configuration options", () => { it('Get and set configuration options', () => {
// https://on.cypress.io/config // https://on.cypress.io/config
let myConfig = Cypress.config(); let myConfig = Cypress.config()
expect(myConfig).to.have.property("animationDistanceThreshold", 5); expect(myConfig).to.have.property('animationDistanceThreshold', 5)
expect(myConfig).to.have.property("baseUrl", null); expect(myConfig).to.have.property('baseUrl', null)
expect(myConfig).to.have.property("defaultCommandTimeout", 4000); expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
expect(myConfig).to.have.property("requestTimeout", 5000); expect(myConfig).to.have.property('requestTimeout', 5000)
expect(myConfig).to.have.property("responseTimeout", 30000); expect(myConfig).to.have.property('responseTimeout', 30000)
expect(myConfig).to.have.property("viewportHeight", 660); expect(myConfig).to.have.property('viewportHeight', 660)
expect(myConfig).to.have.property("viewportWidth", 1000); expect(myConfig).to.have.property('viewportWidth', 1000)
expect(myConfig).to.have.property("pageLoadTimeout", 60000); expect(myConfig).to.have.property('pageLoadTimeout', 60000)
expect(myConfig).to.have.property("waitForAnimations", true); expect(myConfig).to.have.property('waitForAnimations', true)
expect(Cypress.config("pageLoadTimeout")).to.eq(60000); expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
// this will change the config for the rest of your tests! // this will change the config for the rest of your tests!
Cypress.config("pageLoadTimeout", 20000); Cypress.config('pageLoadTimeout', 20000)
expect(Cypress.config("pageLoadTimeout")).to.eq(20000); expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
Cypress.config("pageLoadTimeout", 60000); Cypress.config('pageLoadTimeout', 60000)
}); })
}); })
context("Cypress.dom", () => { context('Cypress.dom', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
// https://on.cypress.io/dom // https://on.cypress.io/dom
it(".isHidden() - determine if a DOM element is hidden", () => { it('.isHidden() - determine if a DOM element is hidden', () => {
let hiddenP = Cypress.$(".dom-p p.hidden").get(0); let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
let visibleP = Cypress.$(".dom-p p.visible").get(0); let visibleP = Cypress.$('.dom-p p.visible').get(0)
// our first paragraph has css class 'hidden' // our first paragraph has css class 'hidden'
expect(Cypress.dom.isHidden(hiddenP)).to.be.true; expect(Cypress.dom.isHidden(hiddenP)).to.be.true
expect(Cypress.dom.isHidden(visibleP)).to.be.false; expect(Cypress.dom.isHidden(visibleP)).to.be.false
}); })
}); })
context("Cypress.env()", () => { context('Cypress.env()', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
// We can set environment variables for highly dynamic values // We can set environment variables for highly dynamic values
// https://on.cypress.io/environment-variables // https://on.cypress.io/environment-variables
it("Get environment variables", () => { it('Get environment variables', () => {
// https://on.cypress.io/env // https://on.cypress.io/env
// set multiple environment variables // set multiple environment variables
Cypress.env({ Cypress.env({
host: "veronica.dev.local", host: 'veronica.dev.local',
api_server: "http://localhost:8888/v1/" api_server: 'http://localhost:8888/v1/',
}); })
// get environment variable // get environment variable
expect(Cypress.env("host")).to.eq("veronica.dev.local"); expect(Cypress.env('host')).to.eq('veronica.dev.local')
// set environment variable // set environment variable
Cypress.env("api_server", "http://localhost:8888/v2/"); Cypress.env('api_server', 'http://localhost:8888/v2/')
expect(Cypress.env("api_server")).to.eq("http://localhost:8888/v2/"); expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
// get all environment variable // get all environment variable
expect(Cypress.env()).to.have.property("host", "veronica.dev.local"); expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
expect(Cypress.env()).to.have.property("api_server", "http://localhost:8888/v2/"); expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
}); })
}); })
context("Cypress.log", () => { context('Cypress.log', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
it("Control what is printed to the Command Log", () => { it('Control what is printed to the Command Log', () => {
// https://on.cypress.io/cypress-log // https://on.cypress.io/cypress-log
}); })
}); })
context("Cypress.platform", () => { context('Cypress.platform', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
it("Get underlying OS name", () => { it('Get underlying OS name', () => {
// https://on.cypress.io/platform // https://on.cypress.io/platform
expect(Cypress.platform).to.be.exist; expect(Cypress.platform).to.be.exist
}); })
}); })
context("Cypress.version", () => { context('Cypress.version', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
it("Get current version of Cypress being run", () => { it('Get current version of Cypress being run', () => {
// https://on.cypress.io/version // https://on.cypress.io/version
expect(Cypress.version).to.be.exist; expect(Cypress.version).to.be.exist
}); })
}); })
context("Cypress.spec", () => { context('Cypress.spec', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/cypress-api"); cy.visit('https://example.cypress.io/cypress-api')
}); })
it("Get current spec information", () => { it('Get current spec information', () => {
// https://on.cypress.io/spec // https://on.cypress.io/spec
// wrap the object so we can inspect it easily by clicking in the command log // wrap the object so we can inspect it easily by clicking in the command log
cy.wrap(Cypress.spec).should("include.keys", ["name", "relative", "absolute"]); cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
}); })
}); })

View File

@@ -3,84 +3,86 @@
/// JSON fixture file can be loaded directly using /// JSON fixture file can be loaded directly using
// the built-in JavaScript bundler // the built-in JavaScript bundler
// @ts-ignore // @ts-ignore
const requiredExample = require("../../fixtures/example"); const requiredExample = require('../../fixtures/example')
context("Files", () => { context('Files', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/files"); cy.visit('https://example.cypress.io/commands/files')
}); })
beforeEach(() => { beforeEach(() => {
// load example.json fixture file and store // load example.json fixture file and store
// in the test context object // in the test context object
cy.fixture("example.json").as("example"); cy.fixture('example.json').as('example')
}); })
it("cy.fixture() - load a fixture", () => { it('cy.fixture() - load a fixture', () => {
// https://on.cypress.io/fixture // https://on.cypress.io/fixture
// Instead of writing a response inline you can // Instead of writing a response inline you can
// use a fixture file's content. // use a fixture file's content.
// when application makes an Ajax request matching "GET **/comments/*" // when application makes an Ajax request matching "GET **/comments/*"
// Cypress will intercept it and reply with the object in `example.json` fixture // Cypress will intercept it and reply with the object in `example.json` fixture
cy.intercept("GET", "**/comments/*", { fixture: "example.json" }).as("getComment"); cy.intercept('GET', '**/comments/*', {fixture: 'example.json'}).as('getComment')
// we have code that gets a comment when // we have code that gets a comment when
// the button is clicked in scripts.js // the button is clicked in scripts.js
cy.get(".fixture-btn").click(); cy.get('.fixture-btn').click()
cy.wait("@getComment") cy.wait('@getComment').its('response.body')
.its("response.body") .should('have.property', 'name')
.should("have.property", "name") .and('include', 'Using fixtures to represent data')
.and("include", "Using fixtures to represent data"); })
});
it("cy.fixture() or require - load a fixture", function () { it('cy.fixture() or require - load a fixture', function () {
// we are inside the "function () { ... }" // we are inside the "function () { ... }"
// callback and can use test context object "this" // callback and can use test context object "this"
// "this.example" was loaded in "beforeEach" function callback // "this.example" was loaded in "beforeEach" function callback
expect(this.example, "fixture in the test context").to.deep.equal(requiredExample); expect(this.example, 'fixture in the test context')
.to.deep.equal(requiredExample)
// or use "cy.wrap" and "should('deep.equal', ...)" assertion // or use "cy.wrap" and "should('deep.equal', ...)" assertion
cy.wrap(this.example).should("deep.equal", requiredExample); cy.wrap(this.example)
}); .should('deep.equal', requiredExample)
})
it("cy.readFile() - read file contents", () => { it('cy.readFile() - read file contents', () => {
// https://on.cypress.io/readfile // https://on.cypress.io/readfile
// You can read a file and yield its contents // You can read a file and yield its contents
// The filePath is relative to your project's root. // The filePath is relative to your project's root.
cy.readFile("cypress.json").then((json) => { cy.readFile('cypress.json').then((json) => {
expect(json).to.be.an("object"); expect(json).to.be.an('object')
}); })
}); })
it("cy.writeFile() - write to a file", () => { it('cy.writeFile() - write to a file', () => {
// https://on.cypress.io/writefile // https://on.cypress.io/writefile
// You can write to a file // You can write to a file
// Use a response from a request to automatically // Use a response from a request to automatically
// generate a fixture file for use later // generate a fixture file for use later
cy.request("https://jsonplaceholder.cypress.io/users").then((response) => { cy.request('https://jsonplaceholder.cypress.io/users')
cy.writeFile("cypress/fixtures/users.json", response.body); .then((response) => {
}); cy.writeFile('cypress/fixtures/users.json', response.body)
})
cy.fixture("users").should((users) => { cy.fixture('users').should((users) => {
expect(users[0].name).to.exist; expect(users[0].name).to.exist
}); })
// JavaScript arrays and objects are stringified // JavaScript arrays and objects are stringified
// and formatted into text. // and formatted into text.
cy.writeFile("cypress/fixtures/profile.json", { cy.writeFile('cypress/fixtures/profile.json', {
id: 8739, id: 8739,
name: "Jane", name: 'Jane',
email: "jane@example.com" email: 'jane@example.com',
}); })
cy.fixture("profile").should((profile) => { cy.fixture('profile').should((profile) => {
expect(profile.name).to.eq("Jane"); expect(profile.name).to.eq('Jane')
}); })
}); })
}); })

View File

@@ -1,58 +1,52 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Local Storage", () => { context('Local Storage', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/local-storage"); cy.visit('https://example.cypress.io/commands/local-storage')
}); })
// Although local storage is automatically cleared // Although local storage is automatically cleared
// in between tests to maintain a clean state // in between tests to maintain a clean state
// sometimes we need to clear the local storage manually // sometimes we need to clear the local storage manually
it("cy.clearLocalStorage() - clear all data in local storage", () => { it('cy.clearLocalStorage() - clear all data in local storage', () => {
// https://on.cypress.io/clearlocalstorage // https://on.cypress.io/clearlocalstorage
cy.get(".ls-btn") cy.get('.ls-btn').click().should(() => {
.click() expect(localStorage.getItem('prop1')).to.eq('red')
.should(() => { expect(localStorage.getItem('prop2')).to.eq('blue')
expect(localStorage.getItem("prop1")).to.eq("red"); expect(localStorage.getItem('prop3')).to.eq('magenta')
expect(localStorage.getItem("prop2")).to.eq("blue"); })
expect(localStorage.getItem("prop3")).to.eq("magenta");
});
// clearLocalStorage() yields the localStorage object // clearLocalStorage() yields the localStorage object
cy.clearLocalStorage().should((ls) => { cy.clearLocalStorage().should((ls) => {
expect(ls.getItem("prop1")).to.be.null; expect(ls.getItem('prop1')).to.be.null
expect(ls.getItem("prop2")).to.be.null; expect(ls.getItem('prop2')).to.be.null
expect(ls.getItem("prop3")).to.be.null; expect(ls.getItem('prop3')).to.be.null
}); })
cy.get(".ls-btn") cy.get('.ls-btn').click().should(() => {
.click() expect(localStorage.getItem('prop1')).to.eq('red')
.should(() => { expect(localStorage.getItem('prop2')).to.eq('blue')
expect(localStorage.getItem("prop1")).to.eq("red"); expect(localStorage.getItem('prop3')).to.eq('magenta')
expect(localStorage.getItem("prop2")).to.eq("blue"); })
expect(localStorage.getItem("prop3")).to.eq("magenta");
});
// Clear key matching string in Local Storage // Clear key matching string in Local Storage
cy.clearLocalStorage("prop1").should((ls) => { cy.clearLocalStorage('prop1').should((ls) => {
expect(ls.getItem("prop1")).to.be.null; expect(ls.getItem('prop1')).to.be.null
expect(ls.getItem("prop2")).to.eq("blue"); expect(ls.getItem('prop2')).to.eq('blue')
expect(ls.getItem("prop3")).to.eq("magenta"); expect(ls.getItem('prop3')).to.eq('magenta')
}); })
cy.get(".ls-btn") cy.get('.ls-btn').click().should(() => {
.click() expect(localStorage.getItem('prop1')).to.eq('red')
.should(() => { expect(localStorage.getItem('prop2')).to.eq('blue')
expect(localStorage.getItem("prop1")).to.eq("red"); expect(localStorage.getItem('prop3')).to.eq('magenta')
expect(localStorage.getItem("prop2")).to.eq("blue"); })
expect(localStorage.getItem("prop3")).to.eq("magenta");
});
// Clear keys matching regex in Local Storage // Clear keys matching regex in Local Storage
cy.clearLocalStorage(/prop1|2/).should((ls) => { cy.clearLocalStorage(/prop1|2/).should((ls) => {
expect(ls.getItem("prop1")).to.be.null; expect(ls.getItem('prop1')).to.be.null
expect(ls.getItem("prop2")).to.be.null; expect(ls.getItem('prop2')).to.be.null
expect(ls.getItem("prop3")).to.eq("magenta"); expect(ls.getItem('prop3')).to.eq('magenta')
}); })
}); })
}); })

View File

@@ -1,32 +1,32 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Location", () => { context('Location', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/location"); cy.visit('https://example.cypress.io/commands/location')
}); })
it("cy.hash() - get the current URL hash", () => { it('cy.hash() - get the current URL hash', () => {
// https://on.cypress.io/hash // https://on.cypress.io/hash
cy.hash().should("be.empty"); cy.hash().should('be.empty')
}); })
it("cy.location() - get window.location", () => { it('cy.location() - get window.location', () => {
// https://on.cypress.io/location // https://on.cypress.io/location
cy.location().should((location) => { cy.location().should((location) => {
expect(location.hash).to.be.empty; expect(location.hash).to.be.empty
expect(location.href).to.eq("https://example.cypress.io/commands/location"); expect(location.href).to.eq('https://example.cypress.io/commands/location')
expect(location.host).to.eq("example.cypress.io"); expect(location.host).to.eq('example.cypress.io')
expect(location.hostname).to.eq("example.cypress.io"); expect(location.hostname).to.eq('example.cypress.io')
expect(location.origin).to.eq("https://example.cypress.io"); expect(location.origin).to.eq('https://example.cypress.io')
expect(location.pathname).to.eq("/commands/location"); expect(location.pathname).to.eq('/commands/location')
expect(location.port).to.eq(""); expect(location.port).to.eq('')
expect(location.protocol).to.eq("https:"); expect(location.protocol).to.eq('https:')
expect(location.search).to.be.empty; expect(location.search).to.be.empty
}); })
}); })
it("cy.url() - get the current URL", () => { it('cy.url() - get the current URL', () => {
// https://on.cypress.io/url // https://on.cypress.io/url
cy.url().should("eq", "https://example.cypress.io/commands/location"); cy.url().should('eq', 'https://example.cypress.io/commands/location')
}); })
}); })

View File

@@ -1,98 +1,106 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Misc", () => { context('Misc', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/misc"); cy.visit('https://example.cypress.io/commands/misc')
}); })
it(".end() - end the command chain", () => { it('.end() - end the command chain', () => {
// https://on.cypress.io/end // https://on.cypress.io/end
// cy.end is useful when you want to end a chain of commands // cy.end is useful when you want to end a chain of commands
// and force Cypress to re-query from the root element // and force Cypress to re-query from the root element
cy.get(".misc-table").within(() => { cy.get('.misc-table').within(() => {
// ends the current chain and yields null // ends the current chain and yields null
cy.contains("Cheryl").click().end(); cy.contains('Cheryl').click().end()
// queries the entire table again // queries the entire table again
cy.contains("Charles").click(); cy.contains('Charles').click()
}); })
}); })
it("cy.exec() - execute a system command", () => { it('cy.exec() - execute a system command', () => {
// execute a system command. // execute a system command.
// so you can take actions necessary for // so you can take actions necessary for
// your test outside the scope of Cypress. // your test outside the scope of Cypress.
// https://on.cypress.io/exec // https://on.cypress.io/exec
// we can use Cypress.platform string to // we can use Cypress.platform string to
// select appropriate command // select appropriate command
// https://on.cypress/io/platform // https://on.cypress/io/platform
cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`); cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
// on CircleCI Windows build machines we have a failure to run bash shell // on CircleCI Windows build machines we have a failure to run bash shell
// https://github.com/cypress-io/cypress/issues/5169 // https://github.com/cypress-io/cypress/issues/5169
// so skip some of the tests by passing flag "--env circle=true" // so skip some of the tests by passing flag "--env circle=true"
const isCircleOnWindows = Cypress.platform === "win32" && Cypress.env("circle"); const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle')
if (isCircleOnWindows) { if (isCircleOnWindows) {
cy.log("Skipping test on CircleCI"); cy.log('Skipping test on CircleCI')
return; return
} }
// cy.exec problem on Shippable CI // cy.exec problem on Shippable CI
// https://github.com/cypress-io/cypress/issues/6718 // https://github.com/cypress-io/cypress/issues/6718
const isShippable = Cypress.platform === "linux" && Cypress.env("shippable"); const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable')
if (isShippable) { if (isShippable) {
cy.log("Skipping test on ShippableCI"); cy.log('Skipping test on ShippableCI')
return; return
} }
cy.exec("echo Jane Lane").its("stdout").should("contain", "Jane Lane"); cy.exec('echo Jane Lane')
.its('stdout').should('contain', 'Jane Lane')
if (Cypress.platform === "win32") { if (Cypress.platform === 'win32') {
cy.exec("print cypress.json").its("stderr").should("be.empty"); cy.exec('print cypress.json')
} else { .its('stderr').should('be.empty')
cy.exec("cat cypress.json").its("stderr").should("be.empty"); } else {
cy.exec('cat cypress.json')
.its('stderr').should('be.empty')
cy.exec("pwd").its("code").should("eq", 0); cy.exec('pwd')
} .its('code').should('eq', 0)
}); }
})
it("cy.focused() - get the DOM element that has focus", () => { it('cy.focused() - get the DOM element that has focus', () => {
// https://on.cypress.io/focused // https://on.cypress.io/focused
cy.get(".misc-form").find("#name").click(); cy.get('.misc-form').find('#name').click()
cy.focused().should("have.id", "name"); cy.focused().should('have.id', 'name')
cy.get(".misc-form").find("#description").click(); cy.get('.misc-form').find('#description').click()
cy.focused().should("have.id", "description"); cy.focused().should('have.id', 'description')
}); })
context("Cypress.Screenshot", function () { context('Cypress.Screenshot', function () {
it("cy.screenshot() - take a screenshot", () => { it('cy.screenshot() - take a screenshot', () => {
// https://on.cypress.io/screenshot // https://on.cypress.io/screenshot
cy.screenshot("my-image"); cy.screenshot('my-image')
}); })
it("Cypress.Screenshot.defaults() - change default config of screenshots", function () { it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
Cypress.Screenshot.defaults({ Cypress.Screenshot.defaults({
blackout: [".foo"], blackout: ['.foo'],
capture: "viewport", capture: 'viewport',
clip: { x: 0, y: 0, width: 200, height: 200 }, clip: {x: 0, y: 0, width: 200, height: 200},
scale: false, scale: false,
disableTimersAndAnimations: true, disableTimersAndAnimations: true,
screenshotOnRunFailure: true, screenshotOnRunFailure: true,
onBeforeScreenshot() {}, onBeforeScreenshot() {
onAfterScreenshot() {} },
}); onAfterScreenshot() {
}); },
}); })
})
})
it("cy.wrap() - wrap an object", () => { it('cy.wrap() - wrap an object', () => {
// https://on.cypress.io/wrap // https://on.cypress.io/wrap
cy.wrap({ foo: "bar" }).should("have.property", "foo").and("include", "bar"); cy.wrap({foo: 'bar'})
}); .should('have.property', 'foo')
}); .and('include', 'bar')
})
})

View File

@@ -1,56 +1,56 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Navigation", () => { context('Navigation', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io"); cy.visit('https://example.cypress.io')
cy.get(".navbar-nav").contains("Commands").click(); cy.get('.navbar-nav').contains('Commands').click()
cy.get(".dropdown-menu").contains("Navigation").click(); cy.get('.dropdown-menu').contains('Navigation').click()
}); })
it("cy.go() - go back or forward in the browser's history", () => { it('cy.go() - go back or forward in the browser\'s history', () => {
// https://on.cypress.io/go // https://on.cypress.io/go
cy.location("pathname").should("include", "navigation"); cy.location('pathname').should('include', 'navigation')
cy.go("back"); cy.go('back')
cy.location("pathname").should("not.include", "navigation"); cy.location('pathname').should('not.include', 'navigation')
cy.go("forward"); cy.go('forward')
cy.location("pathname").should("include", "navigation"); cy.location('pathname').should('include', 'navigation')
// clicking back // clicking back
cy.go(-1); cy.go(-1)
cy.location("pathname").should("not.include", "navigation"); cy.location('pathname').should('not.include', 'navigation')
// clicking forward // clicking forward
cy.go(1); cy.go(1)
cy.location("pathname").should("include", "navigation"); cy.location('pathname').should('include', 'navigation')
}); })
it("cy.reload() - reload the page", () => { it('cy.reload() - reload the page', () => {
// https://on.cypress.io/reload // https://on.cypress.io/reload
cy.reload(); cy.reload()
// reload the page without using the cache // reload the page without using the cache
cy.reload(true); cy.reload(true)
}); })
it("cy.visit() - visit a remote url", () => { it('cy.visit() - visit a remote url', () => {
// https://on.cypress.io/visit // https://on.cypress.io/visit
// Visit any sub-domain of your current domain // Visit any sub-domain of your current domain
// Pass options to the visit // Pass options to the visit
cy.visit("https://example.cypress.io/commands/navigation", { cy.visit('https://example.cypress.io/commands/navigation', {
timeout: 50000, // increase total time for the visit to resolve timeout: 50000, // increase total time for the visit to resolve
onBeforeLoad(contentWindow) { onBeforeLoad(contentWindow) {
// contentWindow is the remote page's window object // contentWindow is the remote page's window object
expect(typeof contentWindow === "object").to.be.true; expect(typeof contentWindow === 'object').to.be.true
}, },
onLoad(contentWindow) { onLoad(contentWindow) {
// contentWindow is the remote page's window object // contentWindow is the remote page's window object
expect(typeof contentWindow === "object").to.be.true; expect(typeof contentWindow === 'object').to.be.true
} },
}); })
}); })
}); })

View File

@@ -1,165 +1,163 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Network Requests", () => { context('Network Requests', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/network-requests"); cy.visit('https://example.cypress.io/commands/network-requests')
});
// Manage HTTP requests in your app
it("cy.request() - make an XHR request", () => {
// https://on.cypress.io/request
cy.request("https://jsonplaceholder.cypress.io/comments").should((response) => {
expect(response.status).to.eq(200);
// the server sometimes gets an extra comment posted from another machine
// which gets returned as 1 extra object
expect(response.body).to.have.property("length").and.be.oneOf([500, 501]);
expect(response).to.have.property("headers");
expect(response).to.have.property("duration");
});
});
it("cy.request() - verify response using BDD syntax", () => {
cy.request("https://jsonplaceholder.cypress.io/comments").then((response) => {
// https://on.cypress.io/assertions
expect(response).property("status").to.equal(200);
expect(response).property("body").to.have.property("length").and.be.oneOf([500, 501]);
expect(response).to.include.keys("headers", "duration");
});
});
it("cy.request() with query parameters", () => {
// will execute request
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
cy.request({
url: "https://jsonplaceholder.cypress.io/comments",
qs: {
postId: 1,
id: 3
}
}) })
.its("body")
.should("be.an", "array")
.and("have.length", 1)
.its("0") // yields first element of the array
.should("contain", {
postId: 1,
id: 3
});
});
it("cy.request() - pass result to the second request", () => { // Manage HTTP requests in your app
// first, let's find out the userId of the first user we have
cy.request("https://jsonplaceholder.cypress.io/users?_limit=1")
.its("body") // yields the response object
.its("0") // yields the first element of the returned list
// the above two commands its('body').its('0')
// can be written as its('body.0')
// if you do not care about TypeScript checks
.then((user) => {
expect(user).property("id").to.be.a("number");
// make a new post on behalf of the user
cy.request("POST", "https://jsonplaceholder.cypress.io/posts", {
userId: user.id,
title: "Cypress Test Runner",
body: "Fast, easy and reliable testing for anything that runs in a browser."
});
})
// note that the value here is the returned value of the 2nd request
// which is the new post object
.then((response) => {
expect(response).property("status").to.equal(201); // new entity created
expect(response).property("body").to.contain({
title: "Cypress Test Runner"
});
// we don't know the exact post id - only that it will be > 100 it('cy.request() - make an XHR request', () => {
// since JSONPlaceholder has built-in 100 posts // https://on.cypress.io/request
expect(response.body).property("id").to.be.a("number").and.to.be.gt(100); cy.request('https://jsonplaceholder.cypress.io/comments')
.should((response) => {
expect(response.status).to.eq(200)
// the server sometimes gets an extra comment posted from another machine
// which gets returned as 1 extra object
expect(response.body).to.have.property('length').and.be.oneOf([500, 501])
expect(response).to.have.property('headers')
expect(response).to.have.property('duration')
})
})
// we don't know the user id here - since it was in above closure it('cy.request() - verify response using BDD syntax', () => {
// so in this test just confirm that the property is there cy.request('https://jsonplaceholder.cypress.io/comments')
expect(response.body).property("userId").to.be.a("number"); .then((response) => {
}); // https://on.cypress.io/assertions
}); expect(response).property('status').to.equal(200)
expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501])
expect(response).to.include.keys('headers', 'duration')
})
})
it("cy.request() - save response in the shared test context", () => { it('cy.request() with query parameters', () => {
// https://on.cypress.io/variables-and-aliases // will execute request
cy.request("https://jsonplaceholder.cypress.io/users?_limit=1") // https://jsonplaceholder.cypress.io/comments?postId=1&id=3
.its("body") cy.request({
.its("0") // yields the first element of the returned list url: 'https://jsonplaceholder.cypress.io/comments',
.as("user") // saves the object in the test context qs: {
.then(function () { postId: 1,
// NOTE 👀 id: 3,
// By the time this callback runs the "as('user')" command },
// has saved the user object in the test context.
// To access the test context we need to use
// the "function () { ... }" callback form,
// otherwise "this" points at a wrong or undefined object!
cy.request("POST", "https://jsonplaceholder.cypress.io/posts", {
userId: this.user.id,
title: "Cypress Test Runner",
body: "Fast, easy and reliable testing for anything that runs in a browser."
}) })
.its("body") .its('body')
.as("post"); // save the new post from the response .should('be.an', 'array')
}) .and('have.length', 1)
.then(function () { .its('0') // yields first element of the array
// When this callback runs, both "cy.request" API commands have finished .should('contain', {
// and the test context has "user" and "post" objects set. postId: 1,
// Let's verify them. id: 3,
expect(this.post, "post has the right user id").property("userId").to.equal(this.user.id); })
}); })
});
it("cy.intercept() - route responses to matching requests", () => { it('cy.request() - pass result to the second request', () => {
// https://on.cypress.io/intercept // first, let's find out the userId of the first user we have
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
.its('body') // yields the response object
.its('0') // yields the first element of the returned list
// the above two commands its('body').its('0')
// can be written as its('body.0')
// if you do not care about TypeScript checks
.then((user) => {
expect(user).property('id').to.be.a('number')
// make a new post on behalf of the user
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
userId: user.id,
title: 'Cypress Test Runner',
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
})
})
// note that the value here is the returned value of the 2nd request
// which is the new post object
.then((response) => {
expect(response).property('status').to.equal(201) // new entity created
expect(response).property('body').to.contain({
title: 'Cypress Test Runner',
})
let message = "whoa, this comment does not exist"; // we don't know the exact post id - only that it will be > 100
// since JSONPlaceholder has built-in 100 posts
expect(response.body).property('id').to.be.a('number')
.and.to.be.gt(100)
// Listen to GET to comments/1 // we don't know the user id here - since it was in above closure
cy.intercept("GET", "**/comments/*").as("getComment"); // so in this test just confirm that the property is there
expect(response.body).property('userId').to.be.a('number')
})
})
// we have code that gets a comment when it('cy.request() - save response in the shared test context', () => {
// the button is clicked in scripts.js // https://on.cypress.io/variables-and-aliases
cy.get(".network-btn").click(); cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
.its('body').its('0') // yields the first element of the returned list
.as('user') // saves the object in the test context
.then(function () {
// NOTE 👀
// By the time this callback runs the "as('user')" command
// has saved the user object in the test context.
// To access the test context we need to use
// the "function () { ... }" callback form,
// otherwise "this" points at a wrong or undefined object!
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
userId: this.user.id,
title: 'Cypress Test Runner',
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
})
.its('body').as('post') // save the new post from the response
})
.then(function () {
// When this callback runs, both "cy.request" API commands have finished
// and the test context has "user" and "post" objects set.
// Let's verify them.
expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id)
})
})
// https://on.cypress.io/wait it('cy.intercept() - route responses to matching requests', () => {
cy.wait("@getComment").its("response.statusCode").should("be.oneOf", [200, 304]); // https://on.cypress.io/intercept
// Listen to POST to comments let message = 'whoa, this comment does not exist'
cy.intercept("POST", "**/comments").as("postComment");
// we have code that posts a comment when // Listen to GET to comments/1
// the button is clicked in scripts.js cy.intercept('GET', '**/comments/*').as('getComment')
cy.get(".network-post").click();
cy.wait("@postComment").should(({ request, response }) => {
expect(request.body).to.include("email");
expect(request.headers).to.have.property("content-type");
expect(response && response.body).to.have.property("name", "Using POST in cy.intercept()");
});
// Stub a response to PUT comments/ **** // we have code that gets a comment when
cy.intercept( // the button is clicked in scripts.js
{ cy.get('.network-btn').click()
method: "PUT",
url: "**/comments/*"
},
{
statusCode: 404,
body: { error: message },
headers: { "access-control-allow-origin": "*" },
delayMs: 500
}
).as("putComment");
// we have code that puts a comment when // https://on.cypress.io/wait
// the button is clicked in scripts.js cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
cy.get(".network-put").click();
cy.wait("@putComment"); // Listen to POST to comments
cy.intercept('POST', '**/comments').as('postComment')
// our 404 statusCode logic in scripts.js executed // we have code that posts a comment when
cy.get(".network-put-comment").should("contain", message); // the button is clicked in scripts.js
}); cy.get('.network-post').click()
}); cy.wait('@postComment').should(({request, response}) => {
expect(request.body).to.include('email')
expect(request.headers).to.have.property('content-type')
expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()')
})
// Stub a response to PUT comments/ ****
cy.intercept({
method: 'PUT',
url: '**/comments/*',
}, {
statusCode: 404,
body: {error: message},
headers: {'access-control-allow-origin': '*'},
delayMs: 500,
}).as('putComment')
// we have code that puts a comment when
// the button is clicked in scripts.js
cy.get('.network-put').click()
cy.wait('@putComment')
// our 404 statusCode logic in scripts.js executed
cy.get('.network-put-comment').should('contain', message)
})
})

View File

@@ -1,100 +1,114 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Querying", () => { context('Querying', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/querying"); cy.visit('https://example.cypress.io/commands/querying')
}); })
// The most commonly used query is 'cy.get()', you can // The most commonly used query is 'cy.get()', you can
// think of this like the '$' in jQuery // think of this like the '$' in jQuery
it("cy.get() - query DOM elements", () => { it('cy.get() - query DOM elements', () => {
// https://on.cypress.io/get // https://on.cypress.io/get
cy.get("#query-btn").should("contain", "Button"); cy.get('#query-btn').should('contain', 'Button')
cy.get(".query-btn").should("contain", "Button"); cy.get('.query-btn').should('contain', 'Button')
cy.get("#querying .well>button:first").should("contain", "Button"); cy.get('#querying .well>button:first').should('contain', 'Button')
// ↲ // ↲
// Use CSS selectors just like jQuery // Use CSS selectors just like jQuery
cy.get('[data-test-id="test-example"]').should("have.class", "example"); cy.get('[data-test-id="test-example"]').should('have.class', 'example')
// 'cy.get()' yields jQuery object, you can get its attribute // 'cy.get()' yields jQuery object, you can get its attribute
// by invoking `.attr()` method // by invoking `.attr()` method
cy.get('[data-test-id="test-example"]').invoke("attr", "data-test-id").should("equal", "test-example"); cy.get('[data-test-id="test-example"]')
.invoke('attr', 'data-test-id')
.should('equal', 'test-example')
// or you can get element's CSS property // or you can get element's CSS property
cy.get('[data-test-id="test-example"]').invoke("css", "position").should("equal", "static"); cy.get('[data-test-id="test-example"]')
.invoke('css', 'position')
.should('equal', 'static')
// or use assertions directly during 'cy.get()' // or use assertions directly during 'cy.get()'
// https://on.cypress.io/assertions // https://on.cypress.io/assertions
cy.get('[data-test-id="test-example"]') cy.get('[data-test-id="test-example"]')
.should("have.attr", "data-test-id", "test-example") .should('have.attr', 'data-test-id', 'test-example')
.and("have.css", "position", "static"); .and('have.css', 'position', 'static')
}); })
it("cy.contains() - query DOM elements with matching content", () => { it('cy.contains() - query DOM elements with matching content', () => {
// https://on.cypress.io/contains // https://on.cypress.io/contains
cy.get(".query-list").contains("bananas").should("have.class", "third"); cy.get('.query-list')
.contains('bananas')
.should('have.class', 'third')
// we can pass a regexp to `.contains()` // we can pass a regexp to `.contains()`
cy.get(".query-list").contains(/^b\w+/).should("have.class", "third"); cy.get('.query-list')
.contains(/^b\w+/)
.should('have.class', 'third')
cy.get(".query-list").contains("apples").should("have.class", "first"); cy.get('.query-list')
.contains('apples')
.should('have.class', 'first')
// passing a selector to contains will // passing a selector to contains will
// yield the selector containing the text // yield the selector containing the text
cy.get("#querying").contains("ul", "oranges").should("have.class", "query-list"); cy.get('#querying')
.contains('ul', 'oranges')
.should('have.class', 'query-list')
cy.get(".query-button").contains("Save Form").should("have.class", "btn"); cy.get('.query-button')
}); .contains('Save Form')
.should('have.class', 'btn')
})
it(".within() - query DOM elements within a specific element", () => { it('.within() - query DOM elements within a specific element', () => {
// https://on.cypress.io/within // https://on.cypress.io/within
cy.get(".query-form").within(() => { cy.get('.query-form').within(() => {
cy.get("input:first").should("have.attr", "placeholder", "Email"); cy.get('input:first').should('have.attr', 'placeholder', 'Email')
cy.get("input:last").should("have.attr", "placeholder", "Password"); cy.get('input:last').should('have.attr', 'placeholder', 'Password')
}); })
}); })
it("cy.root() - query the root DOM element", () => { it('cy.root() - query the root DOM element', () => {
// https://on.cypress.io/root // https://on.cypress.io/root
// By default, root is the document // By default, root is the document
cy.root().should("match", "html"); cy.root().should('match', 'html')
cy.get(".query-ul").within(() => { cy.get('.query-ul').within(() => {
// In this within, the root is now the ul DOM element // In this within, the root is now the ul DOM element
cy.root().should("have.class", "query-ul"); cy.root().should('have.class', 'query-ul')
}); })
}); })
it("best practices - selecting elements", () => { it('best practices - selecting elements', () => {
// https://on.cypress.io/best-practices#Selecting-Elements // https://on.cypress.io/best-practices#Selecting-Elements
cy.get("[data-cy=best-practices-selecting-elements]").within(() => { cy.get('[data-cy=best-practices-selecting-elements]').within(() => {
// Worst - too generic, no context // Worst - too generic, no context
cy.get("button").click(); cy.get('button').click()
// Bad. Coupled to styling. Highly subject to change. // Bad. Coupled to styling. Highly subject to change.
cy.get(".btn.btn-large").click(); cy.get('.btn.btn-large').click()
// Average. Coupled to the `name` attribute which has HTML semantics. // Average. Coupled to the `name` attribute which has HTML semantics.
cy.get("[name=submission]").click(); cy.get('[name=submission]').click()
// Better. But still coupled to styling or JS event listeners. // Better. But still coupled to styling or JS event listeners.
cy.get("#main").click(); cy.get('#main').click()
// Slightly better. Uses an ID but also ensures the element // Slightly better. Uses an ID but also ensures the element
// has an ARIA role attribute // has an ARIA role attribute
cy.get("#main[role=button]").click(); cy.get('#main[role=button]').click()
// Much better. But still coupled to text content that may change. // Much better. But still coupled to text content that may change.
cy.contains("Submit").click(); cy.contains('Submit').click()
// Best. Insulated from all changes. // Best. Insulated from all changes.
cy.get("[data-cy=submit]").click(); cy.get('[data-cy=submit]').click()
}); })
}); })
}); })

View File

@@ -2,202 +2,205 @@
// remove no check once Cypress.sinon is typed // remove no check once Cypress.sinon is typed
// https://github.com/cypress-io/cypress/issues/6720 // https://github.com/cypress-io/cypress/issues/6720
context("Spies, Stubs, and Clock", () => { context('Spies, Stubs, and Clock', () => {
it("cy.spy() - wrap a method in a spy", () => { it('cy.spy() - wrap a method in a spy', () => {
// https://on.cypress.io/spy // https://on.cypress.io/spy
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
const obj = { const obj = {
foo() {} foo() {
}; },
}
const spy = cy.spy(obj, "foo").as("anyArgs"); const spy = cy.spy(obj, 'foo').as('anyArgs')
obj.foo(); obj.foo()
expect(spy).to.be.called; expect(spy).to.be.called
}); })
it("cy.spy() retries until assertions pass", () => { it('cy.spy() retries until assertions pass', () => {
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
const obj = { const obj = {
/** /**
* Prints the argument passed * Prints the argument passed
* @param x {any} * @param x {any}
*/ */
foo(x) { foo(x) {
console.log("obj.foo called with", x); console.log('obj.foo called with', x)
} },
}; }
cy.spy(obj, "foo").as("foo"); cy.spy(obj, 'foo').as('foo')
setTimeout(() => { setTimeout(() => {
obj.foo("first"); obj.foo('first')
}, 500); }, 500)
setTimeout(() => { setTimeout(() => {
obj.foo("second"); obj.foo('second')
}, 2500); }, 2500)
cy.get("@foo").should("have.been.calledTwice"); cy.get('@foo').should('have.been.calledTwice')
}); })
it("cy.stub() - create a stub and/or replace a function with stub", () => { it('cy.stub() - create a stub and/or replace a function with stub', () => {
// https://on.cypress.io/stub // https://on.cypress.io/stub
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
const obj = { const obj = {
/** /**
* prints both arguments to the console * prints both arguments to the console
* @param a {string} * @param a {string}
* @param b {string} * @param b {string}
*/ */
foo(a, b) { foo(a, b) {
console.log("a", a, "b", b); console.log('a', a, 'b', b)
} },
}; }
const stub = cy.stub(obj, "foo").as("foo"); const stub = cy.stub(obj, 'foo').as('foo')
obj.foo("foo", "bar"); obj.foo('foo', 'bar')
expect(stub).to.be.called; expect(stub).to.be.called
}); })
it("cy.clock() - control time in the browser", () => { it('cy.clock() - control time in the browser', () => {
// https://on.cypress.io/clock // https://on.cypress.io/clock
// create the date in UTC so its always the same // create the date in UTC so its always the same
// no matter what local timezone the browser is running in // no matter what local timezone the browser is running in
const now = new Date(Date.UTC(2017, 2, 14)).getTime(); const now = new Date(Date.UTC(2017, 2, 14)).getTime()
cy.clock(now); cy.clock(now)
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
cy.get("#clock-div").click().should("have.text", "1489449600"); cy.get('#clock-div').click()
}); .should('have.text', '1489449600')
})
it("cy.tick() - move time in the browser", () => { it('cy.tick() - move time in the browser', () => {
// https://on.cypress.io/tick // https://on.cypress.io/tick
// create the date in UTC so its always the same // create the date in UTC so its always the same
// no matter what local timezone the browser is running in // no matter what local timezone the browser is running in
const now = new Date(Date.UTC(2017, 2, 14)).getTime(); const now = new Date(Date.UTC(2017, 2, 14)).getTime()
cy.clock(now); cy.clock(now)
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
cy.get("#tick-div").click().should("have.text", "1489449600"); cy.get('#tick-div').click()
.should('have.text', '1489449600')
cy.tick(10000); // 10 seconds passed cy.tick(10000) // 10 seconds passed
cy.get("#tick-div").click().should("have.text", "1489449610"); cy.get('#tick-div').click()
}); .should('have.text', '1489449610')
})
it("cy.stub() matches depending on arguments", () => { it('cy.stub() matches depending on arguments', () => {
// see all possible matchers at // see all possible matchers at
// https://sinonjs.org/releases/latest/matchers/ // https://sinonjs.org/releases/latest/matchers/
const greeter = { const greeter = {
/** /**
* Greets a person * Greets a person
* @param {string} name * @param {string} name
*/ */
greet(name) { greet(name) {
return `Hello, ${name}!`; return `Hello, ${name}!`
} },
}; }
cy.stub(greeter, "greet") cy.stub(greeter, 'greet')
.callThrough() // if you want non-matched calls to call the real method .callThrough() // if you want non-matched calls to call the real method
.withArgs(Cypress.sinon.match.string) .withArgs(Cypress.sinon.match.string).returns('Hi')
.returns("Hi") .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name'))
.withArgs(Cypress.sinon.match.number)
.throws(new Error("Invalid name"));
expect(greeter.greet("World")).to.equal("Hi"); expect(greeter.greet('World')).to.equal('Hi')
// @ts-ignore // @ts-ignore
expect(() => greeter.greet(42)).to.throw("Invalid name"); expect(() => greeter.greet(42)).to.throw('Invalid name')
expect(greeter.greet).to.have.been.calledTwice; expect(greeter.greet).to.have.been.calledTwice
// non-matched calls goes the actual method // non-matched calls goes the actual method
// @ts-ignore // @ts-ignore
expect(greeter.greet()).to.equal("Hello, undefined!"); expect(greeter.greet()).to.equal('Hello, undefined!')
}); })
it("matches call arguments using Sinon matchers", () => { it('matches call arguments using Sinon matchers', () => {
// see all possible matchers at // see all possible matchers at
// https://sinonjs.org/releases/latest/matchers/ // https://sinonjs.org/releases/latest/matchers/
const calculator = { const calculator = {
/** /**
* returns the sum of two arguments * returns the sum of two arguments
* @param a {number} * @param a {number}
* @param b {number} * @param b {number}
*/ */
add(a, b) { add(a, b) {
return a + b; return a + b
} },
}; }
const spy = cy.spy(calculator, "add").as("add"); const spy = cy.spy(calculator, 'add').as('add')
expect(calculator.add(2, 3)).to.equal(5); expect(calculator.add(2, 3)).to.equal(5)
// if we want to assert the exact values used during the call // if we want to assert the exact values used during the call
expect(spy).to.be.calledWith(2, 3); expect(spy).to.be.calledWith(2, 3)
// let's confirm "add" method was called with two numbers // let's confirm "add" method was called with two numbers
expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number); expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number)
// alternatively, provide the value to match // alternatively, provide the value to match
expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)); expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3))
// match any value // match any value
expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3); expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3)
// match any value from a list // match any value from a list
expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3); expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3)
/** /**
* Returns true if the given number is event * Returns true if the given number is event
* @param {number} x * @param {number} x
*/ */
const isEven = (x) => x % 2 === 0; const isEven = (x) => x % 2 === 0
// expect the value to pass a custom predicate function // expect the value to pass a custom predicate function
// the second argument to "sinon.match(predicate, message)" is // the second argument to "sinon.match(predicate, message)" is
// shown if the predicate does not pass and assertion fails // shown if the predicate does not pass and assertion fails
expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, "isEven"), 3); expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3)
/** /**
* Returns a function that checks if a given number is larger than the limit * Returns a function that checks if a given number is larger than the limit
* @param {number} limit * @param {number} limit
* @returns {(x: number) => boolean} * @returns {(x: number) => boolean}
*/ */
const isGreaterThan = (limit) => (x) => x > limit; const isGreaterThan = (limit) => (x) => x > limit
/** /**
* Returns a function that checks if a given number is less than the limit * Returns a function that checks if a given number is less than the limit
* @param {number} limit * @param {number} limit
* @returns {(x: number) => boolean} * @returns {(x: number) => boolean}
*/ */
const isLessThan = (limit) => (x) => x < limit; const isLessThan = (limit) => (x) => x < limit
// you can combine several matchers using "and", "or" // you can combine several matchers using "and", "or"
expect(spy).to.be.calledWith( expect(spy).to.be.calledWith(
Cypress.sinon.match.number, Cypress.sinon.match.number,
Cypress.sinon.match(isGreaterThan(2), "> 2").and(Cypress.sinon.match(isLessThan(4), "< 4")) Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')),
); )
expect(spy).to.be.calledWith( expect(spy).to.be.calledWith(
Cypress.sinon.match.number, Cypress.sinon.match.number,
Cypress.sinon.match(isGreaterThan(200), "> 200").or(Cypress.sinon.match(3)) Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)),
); )
// matchers can be used from BDD assertions // matchers can be used from BDD assertions
cy.get("@add").should("have.been.calledWith", Cypress.sinon.match.number, Cypress.sinon.match(3)); cy.get('@add').should('have.been.calledWith',
Cypress.sinon.match.number, Cypress.sinon.match(3))
// you can alias matchers for shorter test code // you can alias matchers for shorter test code
const { match: M } = Cypress.sinon; const {match: M} = Cypress.sinon
cy.get("@add").should("have.been.calledWith", M.number, M(3)); cy.get('@add').should('have.been.calledWith', M.number, M(3))
}); })
}); })

View File

@@ -1,97 +1,121 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Traversal", () => { context('Traversal', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/traversal"); cy.visit('https://example.cypress.io/commands/traversal')
}); })
it(".children() - get child DOM elements", () => { it('.children() - get child DOM elements', () => {
// https://on.cypress.io/children // https://on.cypress.io/children
cy.get(".traversal-breadcrumb").children(".active").should("contain", "Data"); cy.get('.traversal-breadcrumb')
}); .children('.active')
.should('contain', 'Data')
})
it(".closest() - get closest ancestor DOM element", () => { it('.closest() - get closest ancestor DOM element', () => {
// https://on.cypress.io/closest // https://on.cypress.io/closest
cy.get(".traversal-badge").closest("ul").should("have.class", "list-group"); cy.get('.traversal-badge')
}); .closest('ul')
.should('have.class', 'list-group')
})
it(".eq() - get a DOM element at a specific index", () => { it('.eq() - get a DOM element at a specific index', () => {
// https://on.cypress.io/eq // https://on.cypress.io/eq
cy.get(".traversal-list>li").eq(1).should("contain", "siamese"); cy.get('.traversal-list>li')
}); .eq(1).should('contain', 'siamese')
})
it(".filter() - get DOM elements that match the selector", () => { it('.filter() - get DOM elements that match the selector', () => {
// https://on.cypress.io/filter // https://on.cypress.io/filter
cy.get(".traversal-nav>li").filter(".active").should("contain", "About"); cy.get('.traversal-nav>li')
}); .filter('.active').should('contain', 'About')
})
it(".find() - get descendant DOM elements of the selector", () => { it('.find() - get descendant DOM elements of the selector', () => {
// https://on.cypress.io/find // https://on.cypress.io/find
cy.get(".traversal-pagination").find("li").find("a").should("have.length", 7); cy.get('.traversal-pagination')
}); .find('li').find('a')
.should('have.length', 7)
})
it(".first() - get first DOM element", () => { it('.first() - get first DOM element', () => {
// https://on.cypress.io/first // https://on.cypress.io/first
cy.get(".traversal-table td").first().should("contain", "1"); cy.get('.traversal-table td')
}); .first().should('contain', '1')
})
it(".last() - get last DOM element", () => { it('.last() - get last DOM element', () => {
// https://on.cypress.io/last // https://on.cypress.io/last
cy.get(".traversal-buttons .btn").last().should("contain", "Submit"); cy.get('.traversal-buttons .btn')
}); .last().should('contain', 'Submit')
})
it(".next() - get next sibling DOM element", () => { it('.next() - get next sibling DOM element', () => {
// https://on.cypress.io/next // https://on.cypress.io/next
cy.get(".traversal-ul").contains("apples").next().should("contain", "oranges"); cy.get('.traversal-ul')
}); .contains('apples').next().should('contain', 'oranges')
})
it(".nextAll() - get all next sibling DOM elements", () => { it('.nextAll() - get all next sibling DOM elements', () => {
// https://on.cypress.io/nextall // https://on.cypress.io/nextall
cy.get(".traversal-next-all").contains("oranges").nextAll().should("have.length", 3); cy.get('.traversal-next-all')
}); .contains('oranges')
.nextAll().should('have.length', 3)
})
it(".nextUntil() - get next sibling DOM elements until next el", () => { it('.nextUntil() - get next sibling DOM elements until next el', () => {
// https://on.cypress.io/nextuntil // https://on.cypress.io/nextuntil
cy.get("#veggies").nextUntil("#nuts").should("have.length", 3); cy.get('#veggies')
}); .nextUntil('#nuts').should('have.length', 3)
})
it(".not() - remove DOM elements from set of DOM elements", () => { it('.not() - remove DOM elements from set of DOM elements', () => {
// https://on.cypress.io/not // https://on.cypress.io/not
cy.get(".traversal-disabled .btn").not("[disabled]").should("not.contain", "Disabled"); cy.get('.traversal-disabled .btn')
}); .not('[disabled]').should('not.contain', 'Disabled')
})
it(".parent() - get parent DOM element from DOM elements", () => { it('.parent() - get parent DOM element from DOM elements', () => {
// https://on.cypress.io/parent // https://on.cypress.io/parent
cy.get(".traversal-mark").parent().should("contain", "Morbi leo risus"); cy.get('.traversal-mark')
}); .parent().should('contain', 'Morbi leo risus')
})
it(".parents() - get parent DOM elements from DOM elements", () => { it('.parents() - get parent DOM elements from DOM elements', () => {
// https://on.cypress.io/parents // https://on.cypress.io/parents
cy.get(".traversal-cite").parents().should("match", "blockquote"); cy.get('.traversal-cite')
}); .parents().should('match', 'blockquote')
})
it(".parentsUntil() - get parent DOM elements from DOM elements until el", () => { it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
// https://on.cypress.io/parentsuntil // https://on.cypress.io/parentsuntil
cy.get(".clothes-nav").find(".active").parentsUntil(".clothes-nav").should("have.length", 2); cy.get('.clothes-nav')
}); .find('.active')
.parentsUntil('.clothes-nav')
.should('have.length', 2)
})
it(".prev() - get previous sibling DOM element", () => { it('.prev() - get previous sibling DOM element', () => {
// https://on.cypress.io/prev // https://on.cypress.io/prev
cy.get(".birds").find(".active").prev().should("contain", "Lorikeets"); cy.get('.birds').find('.active')
}); .prev().should('contain', 'Lorikeets')
})
it(".prevAll() - get all previous sibling DOM elements", () => { it('.prevAll() - get all previous sibling DOM elements', () => {
// https://on.cypress.io/prevall // https://on.cypress.io/prevall
cy.get(".fruits-list").find(".third").prevAll().should("have.length", 2); cy.get('.fruits-list').find('.third')
}); .prevAll().should('have.length', 2)
})
it(".prevUntil() - get all previous sibling DOM elements until el", () => { it('.prevUntil() - get all previous sibling DOM elements until el', () => {
// https://on.cypress.io/prevuntil // https://on.cypress.io/prevuntil
cy.get(".foods-list").find("#nuts").prevUntil("#veggies").should("have.length", 3); cy.get('.foods-list').find('#nuts')
}); .prevUntil('#veggies').should('have.length', 3)
})
it(".siblings() - get all sibling DOM elements", () => { it('.siblings() - get all sibling DOM elements', () => {
// https://on.cypress.io/siblings // https://on.cypress.io/siblings
cy.get(".traversal-pills .active").siblings().should("have.length", 2); cy.get('.traversal-pills .active')
}); .siblings().should('have.length', 2)
}); })
})

View File

@@ -1,108 +1,110 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Utilities", () => { context('Utilities', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/utilities"); cy.visit('https://example.cypress.io/utilities')
}); })
it("Cypress._ - call a lodash method", () => { it('Cypress._ - call a lodash method', () => {
// https://on.cypress.io/_ // https://on.cypress.io/_
cy.request("https://jsonplaceholder.cypress.io/users").then((response) => { cy.request('https://jsonplaceholder.cypress.io/users')
let ids = Cypress._.chain(response.body).map("id").take(3).value(); .then((response) => {
let ids = Cypress._.chain(response.body).map('id').take(3).value()
expect(ids).to.deep.eq([1, 2, 3]); expect(ids).to.deep.eq([1, 2, 3])
}); })
}); })
it("Cypress.$ - call a jQuery method", () => { it('Cypress.$ - call a jQuery method', () => {
// https://on.cypress.io/$ // https://on.cypress.io/$
let $li = Cypress.$(".utility-jquery li:first"); let $li = Cypress.$('.utility-jquery li:first')
cy.wrap($li).should("not.have.class", "active").click().should("have.class", "active"); cy.wrap($li)
}); .should('not.have.class', 'active')
.click()
.should('have.class', 'active')
})
it("Cypress.Blob - blob utilities and base64 string conversion", () => { it('Cypress.Blob - blob utilities and base64 string conversion', () => {
// https://on.cypress.io/blob // https://on.cypress.io/blob
cy.get(".utility-blob").then(($div) => { cy.get('.utility-blob').then(($div) => {
// https://github.com/nolanlawson/blob-util#imgSrcToDataURL // https://github.com/nolanlawson/blob-util#imgSrcToDataURL
// get the dataUrl string for the javascript-logo // get the dataUrl string for the javascript-logo
return Cypress.Blob.imgSrcToDataURL( return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
"https://example.cypress.io/assets/img/javascript-logo.png", .then((dataUrl) => {
undefined, // create an <img> element and set its src to the dataUrl
"anonymous" let img = Cypress.$('<img />', {src: dataUrl})
).then((dataUrl) => {
// create an <img> element and set its src to the dataUrl
let img = Cypress.$("<img />", { src: dataUrl });
// need to explicitly return cy here since we are initially returning // need to explicitly return cy here since we are initially returning
// the Cypress.Blob.imgSrcToDataURL promise to our test // the Cypress.Blob.imgSrcToDataURL promise to our test
// append the image // append the image
$div.append(img); $div.append(img)
cy.get(".utility-blob img").click().should("have.attr", "src", dataUrl); cy.get('.utility-blob img').click()
}); .should('have.attr', 'src', dataUrl)
}); })
}); })
})
it("Cypress.minimatch - test out glob patterns against strings", () => { it('Cypress.minimatch - test out glob patterns against strings', () => {
// https://on.cypress.io/minimatch // https://on.cypress.io/minimatch
let matching = Cypress.minimatch("/users/1/comments", "/users/*/comments", { let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
matchBase: true matchBase: true,
}); })
expect(matching, "matching wildcard").to.be.true; expect(matching, 'matching wildcard').to.be.true
matching = Cypress.minimatch("/users/1/comments/2", "/users/*/comments", { matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
matchBase: true matchBase: true,
}); })
expect(matching, "comments").to.be.false; expect(matching, 'comments').to.be.false
// ** matches against all downstream path segments // ** matches against all downstream path segments
matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/**", { matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
matchBase: true matchBase: true,
}); })
expect(matching, "comments").to.be.true; expect(matching, 'comments').to.be.true
// whereas * matches only the next path segment // whereas * matches only the next path segment
matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/*", { matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
matchBase: false matchBase: false,
}); })
expect(matching, "comments").to.be.false; expect(matching, 'comments').to.be.false
}); })
it("Cypress.Promise - instantiate a bluebird promise", () => { it('Cypress.Promise - instantiate a bluebird promise', () => {
// https://on.cypress.io/promise // https://on.cypress.io/promise
let waited = false; let waited = false
/** /**
* @return Bluebird<string> * @return Bluebird<string>
*/ */
function waitOneSecond() { function waitOneSecond() {
// return a promise that resolves after 1 second // return a promise that resolves after 1 second
// @ts-ignore TS2351 (new Cypress.Promise) // @ts-ignore TS2351 (new Cypress.Promise)
return new Cypress.Promise((resolve, reject) => { return new Cypress.Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
// set waited to true // set waited to true
waited = true; waited = true
// resolve with 'foo' string // resolve with 'foo' string
resolve("foo"); resolve('foo')
}, 1000); }, 1000)
}); })
} }
cy.then(() => { cy.then(() => {
// return a promise to cy.then() that // return a promise to cy.then() that
// is awaited until it resolves // is awaited until it resolves
// @ts-ignore TS7006 // @ts-ignore TS7006
return waitOneSecond().then((str) => { return waitOneSecond().then((str) => {
expect(str).to.eq("foo"); expect(str).to.eq('foo')
expect(waited).to.be.true; expect(waited).to.be.true
}); })
}); })
}); })
}); })

View File

@@ -1,59 +1,59 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Viewport", () => { context('Viewport', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/viewport"); cy.visit('https://example.cypress.io/commands/viewport')
}); })
it("cy.viewport() - set the viewport size and dimension", () => { it('cy.viewport() - set the viewport size and dimension', () => {
// https://on.cypress.io/viewport // https://on.cypress.io/viewport
cy.get("#navbar").should("be.visible"); cy.get('#navbar').should('be.visible')
cy.viewport(320, 480); cy.viewport(320, 480)
// the navbar should have collapse since our screen is smaller // the navbar should have collapse since our screen is smaller
cy.get("#navbar").should("not.be.visible"); cy.get('#navbar').should('not.be.visible')
cy.get(".navbar-toggle").should("be.visible").click(); cy.get('.navbar-toggle').should('be.visible').click()
cy.get(".nav").find("a").should("be.visible"); cy.get('.nav').find('a').should('be.visible')
// lets see what our app looks like on a super large screen // lets see what our app looks like on a super large screen
cy.viewport(2999, 2999); cy.viewport(2999, 2999)
// cy.viewport() accepts a set of preset sizes // cy.viewport() accepts a set of preset sizes
// to easily set the screen to a device's width and height // to easily set the screen to a device's width and height
// We added a cy.wait() between each viewport change so you can see // We added a cy.wait() between each viewport change so you can see
// the change otherwise it is a little too fast to see :) // the change otherwise it is a little too fast to see :)
cy.viewport("macbook-15"); cy.viewport('macbook-15')
cy.wait(200); cy.wait(200)
cy.viewport("macbook-13"); cy.viewport('macbook-13')
cy.wait(200); cy.wait(200)
cy.viewport("macbook-11"); cy.viewport('macbook-11')
cy.wait(200); cy.wait(200)
cy.viewport("ipad-2"); cy.viewport('ipad-2')
cy.wait(200); cy.wait(200)
cy.viewport("ipad-mini"); cy.viewport('ipad-mini')
cy.wait(200); cy.wait(200)
cy.viewport("iphone-6+"); cy.viewport('iphone-6+')
cy.wait(200); cy.wait(200)
cy.viewport("iphone-6"); cy.viewport('iphone-6')
cy.wait(200); cy.wait(200)
cy.viewport("iphone-5"); cy.viewport('iphone-5')
cy.wait(200); cy.wait(200)
cy.viewport("iphone-4"); cy.viewport('iphone-4')
cy.wait(200); cy.wait(200)
cy.viewport("iphone-3"); cy.viewport('iphone-3')
cy.wait(200); cy.wait(200)
// cy.viewport() accepts an orientation for all presets // cy.viewport() accepts an orientation for all presets
// the default orientation is 'portrait' // the default orientation is 'portrait'
cy.viewport("ipad-2", "portrait"); cy.viewport('ipad-2', 'portrait')
cy.wait(200); cy.wait(200)
cy.viewport("iphone-4", "landscape"); cy.viewport('iphone-4', 'landscape')
cy.wait(200); cy.wait(200)
// The viewport will be reset back to the default dimensions // The viewport will be reset back to the default dimensions
// in between tests (the default can be set in cypress.json) // in between tests (the default can be set in cypress.json)
}); })
}); })

View File

@@ -1,31 +1,31 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Waiting", () => { context('Waiting', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/waiting"); cy.visit('https://example.cypress.io/commands/waiting')
}); })
// BE CAREFUL of adding unnecessary wait times. // BE CAREFUL of adding unnecessary wait times.
// https://on.cypress.io/best-practices#Unnecessary-Waiting // https://on.cypress.io/best-practices#Unnecessary-Waiting
// https://on.cypress.io/wait // https://on.cypress.io/wait
it("cy.wait() - wait for a specific amount of time", () => { it('cy.wait() - wait for a specific amount of time', () => {
cy.get(".wait-input1").type("Wait 1000ms after typing"); cy.get('.wait-input1').type('Wait 1000ms after typing')
cy.wait(1000); cy.wait(1000)
cy.get(".wait-input2").type("Wait 1000ms after typing"); cy.get('.wait-input2').type('Wait 1000ms after typing')
cy.wait(1000); cy.wait(1000)
cy.get(".wait-input3").type("Wait 1000ms after typing"); cy.get('.wait-input3').type('Wait 1000ms after typing')
cy.wait(1000); cy.wait(1000)
}); })
it("cy.wait() - wait for a specific route", () => { it('cy.wait() - wait for a specific route', () => {
// Listen to GET to comments/1 // Listen to GET to comments/1
cy.intercept("GET", "**/comments/*").as("getComment"); cy.intercept('GET', '**/comments/*').as('getComment')
// we have code that gets a comment when // we have code that gets a comment when
// the button is clicked in scripts.js // the button is clicked in scripts.js
cy.get(".network-btn").click(); cy.get('.network-btn').click()
// wait for GET comments/1 // wait for GET comments/1
cy.wait("@getComment").its("response.statusCode").should("be.oneOf", [200, 304]); cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
}); })
}); })

View File

@@ -1,22 +1,22 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
context("Window", () => { context('Window', () => {
beforeEach(() => { beforeEach(() => {
cy.visit("https://example.cypress.io/commands/window"); cy.visit('https://example.cypress.io/commands/window')
}); })
it("cy.window() - get the global window object", () => { it('cy.window() - get the global window object', () => {
// https://on.cypress.io/window // https://on.cypress.io/window
cy.window().should("have.property", "top"); cy.window().should('have.property', 'top')
}); })
it("cy.document() - get the document object", () => { it('cy.document() - get the document object', () => {
// https://on.cypress.io/document // https://on.cypress.io/document
cy.document().should("have.property", "charset").and("eq", "UTF-8"); cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
}); })
it("cy.title() - get the title", () => { it('cy.title() - get the title', () => {
// https://on.cypress.io/title // https://on.cypress.io/title
cy.title().should("include", "Kitchen Sink"); cy.title().should('include', 'Kitchen Sink')
}); })
}); })

View File

@@ -1,6 +1,6 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
// *********************************************************** // ***********************************************************
// This example plugins/index.jsx can be used to load plugins // This example plugins/index.js can be used to load plugins
// //
// You can change the location of this file or turn off loading // You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option. // the plugins file with the 'pluginsFile' configuration option.
@@ -17,6 +17,6 @@
*/ */
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
module.exports = (on, config) => { module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config
}; }

View File

@@ -1,5 +1,5 @@
// *********************************************************** // ***********************************************************
// This example support/index.jsx is processed and // This example support/index.js is processed and
// loaded automatically before your test files. // loaded automatically before your test files.
// //
// This is a great place to put global configuration and // This is a great place to put global configuration and
@@ -14,7 +14,7 @@
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import "./commands"; import './commands'
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')

View File

@@ -2,7 +2,11 @@
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"baseUrl": "../node_modules", "baseUrl": "../node_modules",
"types": ["cypress"] "types": [
"cypress"
]
}, },
"include": ["**/*.*"] "include": [
"**/*.*"
]
} }

View File

@@ -1,2 +0,0 @@
if ("serviceWorker" in navigator)
navigator.serviceWorker.register("/dev-sw.js?dev-sw", { scope: "/", type: "classic" });

View File

@@ -1,96 +0,0 @@
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// If the loader is already loaded, just stop.
if (!self.define) {
let registry = {};
// Used for `eval` and `importScripts` where we can't get script URL by other means.
// In both cases, it's safe to use a global var because those functions are synchronous.
let nextDefineUri;
const singleRequire = (uri, parentUri) => {
uri = new URL(uri + ".js", parentUri).href;
return (
registry[uri] ||
new Promise((resolve) => {
if ("document" in self) {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
} else {
nextDefineUri = uri;
importScripts(uri);
resolve();
}
}).then(() => {
let promise = registry[uri];
if (!promise) {
throw new Error(`Module ${uri} didnt register its module`);
}
return promise;
})
);
};
self.define = (depsNames, factory) => {
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
if (registry[uri]) {
// Module is already loading or loaded.
return;
}
let exports = {};
const require = (depUri) => singleRequire(depUri, uri);
const specialDeps = {
module: { uri },
exports,
require
};
registry[uri] = Promise.all(depsNames.map((depName) => specialDeps[depName] || require(depName))).then((deps) => {
factory(...deps);
return exports;
});
};
}
define(["./workbox-b5f7729d"], function (workbox) {
"use strict";
self.skipWaiting();
workbox.clientsClaim();
/**
* The precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
workbox.precacheAndRoute(
[
{
url: "registerSW.js",
revision: "3ca0b8505b4bec776b69afdba2768812"
},
{
url: "index.html",
revision: "0.sa702m4aq68"
}
],
{}
);
workbox.cleanupOutdatedCaches();
workbox.registerRoute(
new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
allowlist: [/^\/$/]
})
);
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,126 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<link rel="icon" href="/favicon.png"/>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<link rel="icon" href="/ro-favicon.png"/>
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<link rel="icon" href="/pm/pm-favicon.ico"/>
<% } %>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#1690ff"/>
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<!-- TODO:AIo Update the individual logos for each.-->
<link rel="apple-touch-icon" href="/logo192.png"/>
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<meta name="description" content="ImEX Online"/>
<title>ImEX Online</title>
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
(function () {
d = document;
s = d.createElement('script');
s.src = 'https://client.crisp.chat/l.js';
s.async = 1;
d.getElementsByTagName('head')[0].appendChild(s);
})();
</script>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<meta name="description" content="Rome Online"/>
<title>Rome Online</title>
<script type="text/javascript" id="zsiqchat">
var $zoho = $zoho || {};
$zoho.salesiq = $zoho.salesiq || {
widgetcode: "siq01bb8ac617280bdacddfeb528f07734dadc64ef3f05efef9f769c1ec171af666",
values: {},
ready: function () {
}
};
var d = document;
s = d.createElement("script");
s.type = "text/javascript";
s.id = "zsiqscript";
s.defer = true;
s.src = "https://salesiq.zohopublic.com/widget";
t = d.getElementsByTagName("script")[0];
t.parentNode.insertBefore(s, t);
</script>
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<title>ProManager</title>
<meta name="description" content="ProManager"/>
<% } %>
<script>
!(function () {
'use strict';
var e = [
'debug',
'destroy',
'do',
'help',
'identify',
'is',
'off',
'on',
'ready',
'render',
'reset',
'safe',
'set',
];
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
else {
var n = (window.noticeable = window.noticeable || []);
function t(e) {
return function () {
var t = Array.prototype.slice.call(arguments);
return t.unshift(e), n.push(t), n;
};
}
!(function () {
for (var o = 0; o < e.length; o++) {
var r = e[o];
n[r] = t(r);
}
})(),
(function () {
var e = document.createElement('script');
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
var n = document.head;
n.insertBefore(e, n.firstChild);
})();
}
})();
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="src/index.jsx"></script>
</body>
</html>

19229
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,110 +1,103 @@
{ {
"name": "bodyshop", "name": "bodyshop",
"version": "0.2.1", "version": "0.2.1",
"engines": {
"node": ">=18.18.2"
},
"type": "module",
"private": true, "private": true,
"proxy": "http://localhost:4000", "proxy": "http://localhost:4000",
"dependencies": { "dependencies": {
"@ant-design/pro-layout": "^7.19.12", "@ant-design/compatible": "^5.1.2",
"@apollo/client": "^3.11.8", "@ant-design/pro-layout": "^7.17.16",
"@emotion/is-prop-valid": "^1.3.1", "@apollo/client": "^3.8.10",
"@fingerprintjs/fingerprintjs": "^4.5.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.1.0",
"@fingerprintjs/fingerprintjs": "^4.2.2",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.1",
"@sentry/cli": "^2.36.2", "@sentry/cli": "^2.28.6",
"@sentry/react": "^7.114.0", "@sentry/react": "^7.104.0",
"@splitsoftware/splitio-react": "^1.13.0", "@sentry/tracing": "^7.104.0",
"@splitsoftware/splitio-react": "^1.11.0",
"@tanem/react-nprogress": "^5.0.51", "@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1", "antd": "^5.14.2",
"antd": "^5.20.1",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0", "apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1", "axios": "^1.6.7",
"axios": "^1.7.7", "craco-less": "^3.0.1",
"classnames": "^2.5.1", "dayjs": "^1.11.10",
"css-box-model": "^1.2.1",
"dayjs": "^1.11.13",
"dayjs-business-days2": "^1.2.2", "dayjs-business-days2": "^1.2.2",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^10.13.2", "firebase": "^10.8.1",
"graphql": "^16.9.0", "graphql": "^16.6.0",
"i18next": "^23.15.1", "i18next": "^23.10.0",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^7.0.2",
"immutability-helper": "^3.1.1", "jsoneditor": "^10.0.1",
"libphonenumber-js": "^1.11.9", "jsreport-browser-client-dist": "^1.3.0",
"logrocket": "^8.1.2", "libphonenumber-js": "^1.10.57",
"markerjs2": "^2.32.2", "logrocket": "^8.0.1",
"memoize-one": "^6.0.0", "markerjs2": "^2.32.0",
"normalize-url": "^8.0.1", "normalize-url": "^8.0.0",
"object-hash": "^3.0.0", "phone": "^3.1.42",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^9.1.0", "query-string": "^9.0.0",
"raf-schd": "^4.0.3", "rc-queue-anim": "^2.0.0",
"react": "^18.3.1", "rc-scroll-anim": "^2.7.6",
"react-big-calendar": "^1.14.1", "react": "^18.2.0",
"react-big-calendar": "^1.11.0",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^7.2.0", "react-cookie": "^7.1.0",
"react-dom": "^18.3.1", "react-dom": "^18.2.0",
"react-drag-listview": "^2.0.0", "react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.1", "react-grid-gallery": "^1.0.0",
"react-grid-layout": "1.3.4", "react-grid-layout": "1.3.4",
"react-i18next": "^14.1.3", "react-i18next": "^14.0.5",
"react-icons": "^5.3.0", "react-icons": "^5.0.1",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-intersection-observer": "^9.8.1",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-number-format": "^5.4.2", "react-number-format": "^5.3.3",
"react-popopo": "^2.1.9", "react-redux": "^9.1.0",
"react-product-fruits": "^2.2.61",
"react-redux": "^9.1.2",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.22.2",
"react-scripts": "^5.0.1",
"react-sticky": "^6.0.3", "react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.5", "react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.4", "recharts": "^2.12.2",
"recharts": "^2.12.7",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-actions": "^3.0.3",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.3.0", "redux-saga": "^1.3.0",
"redux-state-sync": "^3.1.4", "redux-state-sync": "^3.1.4",
"reselect": "^5.1.1", "reselect": "^5.1.0",
"sass": "^1.79.3", "sass": "^1.71.1",
"socket.io-client": "^4.8.0", "socket.io-client": "^4.7.4",
"styled-components": "^6.1.13", "styled-components": "^6.1.8",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"use-memo-one": "^1.1.3", "terser-webpack-plugin": "^5.3.10",
"userpilot": "^1.3.6", "web-vitals": "^3.5.2",
"vite-plugin-ejs": "^1.7.0", "workbox-core": "^7.0.0",
"web-vitals": "^3.5.2" "workbox-expiration": "^7.0.0",
"workbox-navigation-preload": "^7.0.0",
"workbox-precaching": "^7.0.0",
"workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0",
"yauzl": "^3.1.1"
}, },
"scripts": { "scripts": {
"postinstall": "echo 'when updating react-big-calendar, remember to check to localizer in the calendar wrapper'",
"analyze": "source-map-explorer 'build/static/js/*.js'", "analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "vite", "start": "craco start",
"build": "dotenvx run --env-file=.env.development.imex -- vite build", "build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite", "build:test": "env-cmd -f .env.test npm run build",
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite", "build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite", "buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"preview:imex": "dotenvx run --env-file=.env.development.imex -- vite preview",
"preview:rome": "dotenvx run --env-file=.env.development.rome -- vite preview",
"preview:promanager": "dotenvx run --env-file=.env.development.promanager -- vite preview",
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
"build:production:promanager": "env-cmd -f .env.production.promanager npm run build",
"test": "cypress open", "test": "cypress open",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .", "madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
"eulaize": "node src/utils/eulaize.js", "eulaize": "node src/utils/eulaize.js",
"sentry:sourcemaps:imex": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build" "sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@@ -128,36 +121,14 @@
"resolutions": { "resolutions": {
"react-error-overlay": "6.0.9" "react-error-overlay": "6.0.9"
}, },
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.6.1"
},
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.24.7", "@sentry/webpack-plugin": "^2.14.2",
"@dotenvx/dotenvx": "^1.14.1", "@testing-library/cypress": "^10.0.1",
"@emotion/babel-plugin": "^11.12.0", "cypress": "^13.6.6",
"@emotion/react": "^11.13.3",
"@sentry/webpack-plugin": "^2.22.4",
"@testing-library/cypress": "^10.0.2",
"browserslist": "^4.23.3",
"browserslist-to-esbuild": "^2.1.1",
"chalk": "^5.3.0",
"cross-env": "^7.0.3",
"cypress": "^13.14.2",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.15.1", "eslint-plugin-cypress": "^2.15.1",
"memfs": "^4.12.0",
"os-browserify": "^0.3.0",
"react-error-overlay": "6.0.11", "react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3"
"vite": "^5.4.7",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-pwa": "^0.20.5",
"vite-plugin-style-import": "^2.0.0",
"workbox-window": "^7.1.0"
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -16422,7 +16422,7 @@ For when you don't want to write the same thing over and over to cache a method
$ npm install --save-dev stubs $ npm install --save-dev stubs
``` ```
```js ```js
var mylib = require('./lib/index.jsx') var mylib = require('./lib/index.js')
var stubs = require('stubs') var stubs = require('stubs')
// make it a noop // make it a noop

View File

@@ -16567,7 +16567,7 @@ even more slower.
## Benchmarks ## Benchmarks
```bash ```bash
$ node benchmarks/index.jsx $ node benchmarks/index.js
Benchmarking: sign Benchmarking: sign
elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled) elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled)
eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled) eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled)

95
client/public/editor.js Normal file
View File

@@ -0,0 +1,95 @@
// unlayer.registerPropertyEditor({
// name: "field_name",
// layout: "bottom",
// Widget: unlayer.createWidget({
// render(value) {
// return `
// <input class="field" value=${value} />
// `;
// },
// mount(node, value, updateValue) {
// var input = node.getElementsByClassName("field")[0];
// input.onchange = function (event) {
// updateValue(event.target.value);
// };
// },
// }),
// });
// unlayer.registerTool({
// type: "whatever",
// category: "contents",
// label: "Begin Repeat",
// icon: "fa-smile",
// values: {},
// options: {
// default: {
// title: null,
// },
// text: {
// title: "Field",
// position: 1,
// options: {
// field: {
// label: "Field",
// defaultValue: "",
// widget: "field_name",
// },
// },
// },
// },
// renderer: {
// Viewer: unlayer.createViewer({
// render(values) {
// console.log(values);
// return `
// <div style="display: none;">{{#each ${values.field}}}</div>
// `;
// },
// }),
// exporters: {
// web: function () {},
// email: function () {},
// },
// },
// });
// unlayer.registerTool({
// type: "whatever",
// category: "contents",
// label: "End Repeat",
// icon: "fa-smile",
// values: {},
// options: {
// default: {
// title: null,
// },
// text: {
// title: "Field",
// position: 1,
// options: {
// field: {
// label: "Field",
// defaultValue: "",
// widget: "field_name",
// },
// },
// },
// },
// renderer: {
// Viewer: unlayer.createViewer({
// render(values) {
// return `
// <div style="display: none;">{{ /each }}</div>
// `;
// },
// }),
// exporters: {
// web: function () {},
// email: function () {},
// },
// },
// });
unlayer.registerColumns([2, 2, 2, 2, 2, 2]);
unlayer.registerColumns([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);

View File

@@ -5,41 +5,41 @@ importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
// Initialize the Firebase app in the service worker by passing the generated config // Initialize the Firebase app in the service worker by passing the generated config
let firebaseConfig; let firebaseConfig;
switch (this.location.hostname) { switch (this.location.hostname) {
case "localhost": case "localhost":
firebaseConfig = { firebaseConfig = {
apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc", apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
authDomain: "imex-dev.firebaseapp.com", authDomain: "imex-dev.firebaseapp.com",
databaseURL: "https://imex-dev.firebaseio.com", databaseURL: "https://imex-dev.firebaseio.com",
projectId: "imex-dev", projectId: "imex-dev",
storageBucket: "imex-dev.appspot.com", storageBucket: "imex-dev.appspot.com",
messagingSenderId: "759548147434", messagingSenderId: "759548147434",
appId: "1:759548147434:web:e8239868a48ceb36700993", appId: "1:759548147434:web:e8239868a48ceb36700993",
measurementId: "G-K5XRBVVB4S", measurementId: "G-K5XRBVVB4S",
}; };
break; break;
case "test.imex.online": case "test.imex.online":
firebaseConfig = { firebaseConfig = {
apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c",
authDomain: "imex-test.firebaseapp.com", authDomain: "imex-test.firebaseapp.com",
projectId: "imex-test", projectId: "imex-test",
storageBucket: "imex-test.appspot.com", storageBucket: "imex-test.appspot.com",
messagingSenderId: "991923618608", messagingSenderId: "991923618608",
appId: "1:991923618608:web:633437569cdad78299bef5", appId: "1:991923618608:web:633437569cdad78299bef5",
// measurementId: "${config.measurementId}", // measurementId: "${config.measurementId}",
}; };
break; break;
case "imex.online": case "imex.online":
default: default:
firebaseConfig = { firebaseConfig = {
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU", apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
authDomain: "imex-prod.firebaseapp.com", authDomain: "imex-prod.firebaseapp.com",
databaseURL: "https://imex-prod.firebaseio.com", databaseURL: "https://imex-prod.firebaseio.com",
projectId: "imex-prod", projectId: "imex-prod",
storageBucket: "imex-prod.appspot.com", storageBucket: "imex-prod.appspot.com",
messagingSenderId: "253497221485", messagingSenderId: "253497221485",
appId: "1:253497221485:web:3c81c483b94db84b227a64", appId: "1:253497221485:web:3c81c483b94db84b227a64",
measurementId: "G-NTWBKG2L0M", measurementId: "G-NTWBKG2L0M",
}; };
} }
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);
@@ -48,9 +48,9 @@ firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging(); const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) { messaging.onBackgroundMessage(function (payload) {
// Customize notification here // Customize notification here
const channel = new BroadcastChannel("imex-sw-messages"); const channel = new BroadcastChannel("imex-sw-messages");
channel.postMessage(payload); channel.postMessage(payload);
//self.registration.showNotification(notificationTitle, notificationOptions); //self.registration.showNotification(notificationTitle, notificationOptions);
}); });

88
client/public/index.html Normal file
View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link href="%PUBLIC_URL%/favicon.png" rel="icon"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta content="#002366" name="theme-color"/>
<meta content="ImEX Online" name="description"/>
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<link href="logo192.png" rel="apple-touch-icon"/>
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
</script>
<script>
!(function () {
"use strict";
var e = [
"debug",
"destroy",
"do",
"help",
"identify",
"is",
"off",
"on",
"ready",
"render",
"reset",
"safe",
"set",
];
if (window.noticeable)
console.warn("Noticeable SDK code snippet loaded more than once");
else {
var n = (window.noticeable = window.noticeable || []);
function t(e) {
return function () {
var t = Array.prototype.slice.call(arguments);
return t.unshift(e), n.push(t), n;
};
}
!(function () {
for (var o = 0; o < e.length; o++) {
var r = e[o];
n[r] = t(r);
}
})(),
(function () {
var e = document.createElement("script");
(e.async = !0), (e.src = "https://sdk.noticeable.io/l.js");
var n = document.head;
n.insertBefore(e, n.firstChild);
})();
}
})();
</script>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link href="%PUBLIC_URL%/manifest.json" rel="manifest"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>ImEX Online</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,27 @@
{
"short_name": "ImEX Online",
"name": "ImEX Online",
"description": "The ultimate bodyshop management system.",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo1024.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#fff",
"background_color": "#fff",
"gcm_sender_id": "103953800507"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

View File

@@ -1,58 +1,53 @@
import { ApolloProvider } from "@apollo/client"; import {ApolloProvider} from "@apollo/client";
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react"; import {SplitFactoryProvider, SplitSdk,} from '@splitsoftware/splitio-react';
import { ConfigProvider } from "antd"; import {ConfigProvider} from "antd";
import enLocale from "antd/es/locale/en_US"; import enLocale from "antd/es/locale/en_US";
import dayjs from "../utils/day"; import dayjs from "../utils/day";
import "dayjs/locale/en"; import 'dayjs/locale/en';
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component"; import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient"; import client from "../utils/GraphQLClient";
import App from "./App"; import App from "./App";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import themeProvider from "./themeProvider"; import themeProvider from "./themeProvider";
import { Userpilot } from "userpilot";
// Initialize Userpilot
if (import.meta.env.DEV) {
Userpilot.initialize("NX-69145f08");
}
dayjs.locale("en"); dayjs.locale("en");
const config = { const config = {
core: { core: {
authorizationKey: import.meta.env.VITE_APP_SPLIT_API, authorizationKey: process.env.REACT_APP_SPLIT_API,
key: "anon" key: "anon",
} },
}; };
export const factory = SplitSdk(config); export const factory = SplitSdk(config);
function AppContainer() {
const { t } = useTranslation();
return ( function AppContainer() {
<ApolloProvider client={client}> const {t} = useTranslation();
<ConfigProvider
//componentSize="small" return (
input={{ autoComplete: "new-password" }} <ApolloProvider client={client}>
locale={enLocale} <ConfigProvider
theme={themeProvider} //componentSize="small"
form={{ input={{autoComplete: "new-password"}}
validateMessages: { locale={enLocale}
// eslint-disable-next-line no-template-curly-in-string theme={themeProvider}
required: t("general.validation.required", { label: "${label}" }) form={{
} validateMessages: {
}} // eslint-disable-next-line no-template-curly-in-string
> required: t("general.validation.required", {label: "${label}"}),
<GlobalLoadingBar /> },
<SplitFactoryProvider factory={factory}> }}
<App /> >
</SplitFactoryProvider> <GlobalLoadingBar/>
</ConfigProvider> <SplitFactoryProvider factory={factory}>
</ApolloProvider> <App/>
); </SplitFactoryProvider>
</ConfigProvider>
</ApolloProvider>
);
} }
export default Sentry.withProfiler(AppContainer); export default Sentry.withProfiler(AppContainer);

View File

@@ -1,233 +1,156 @@
import { useSplitClient } from "@splitsoftware/splitio-react"; import {useSplitClient} from "@splitsoftware/splitio-react";
import { Button, Result } from "antd"; import {Button, Result} from "antd";
import LogRocket from "logrocket"; import LogRocket from "logrocket";
import React, { lazy, Suspense, useEffect, useState } from "react"; import React, {lazy, Suspense, useEffect, useState} from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { Route, Routes } from "react-router-dom"; import {Route, Routes} from "react-router-dom";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import DocumentEditorContainer from "../components/document-editor/document-editor.container"; import DocumentEditorContainer from "../components/document-editor/document-editor.container";
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports import ErrorBoundary from "../components/error-boundary/error-boundary.component";
//Component Imports
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
import DisclaimerPage from "../pages/disclaimer/disclaimer.page"; import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
import LandingPage from "../pages/landing/landing.page"; import LandingPage from "../pages/landing/landing.page";
import TechPageContainer from "../pages/tech/tech.page.container"; import TechPageContainer from "../pages/tech/tech.page.container";
import { setOnline } from "../redux/application/application.actions"; import {setOnline} from "../redux/application/application.actions";
import { selectOnline } from "../redux/application/application.selectors"; import {selectOnline} from "../redux/application/application.selectors";
import { checkUserSession } from "../redux/user/user.actions"; import {checkUserSession} from "../redux/user/user.actions";
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors"; import {selectBodyshop, selectCurrentEula, selectCurrentUser,} from "../redux/user/user.selectors";
import PrivateRoute from "../components/PrivateRoute"; import PrivateRoute from "../components/PrivateRoute";
import "./App.styles.scss"; import "./App.styles.scss";
import handleBeta from "../utils/betaHandler";
import Eula from "../components/eula/eula.component"; import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr";
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component")); const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component")
);
const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page")); const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
const CsiPage = lazy(() => import("../pages/csi/csi.container.page")); const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container")); const MobilePaymentContainer = lazy(() =>
import("../pages/mobile-payment/mobile-payment.container")
);
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
online: selectOnline, online: selectOnline,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentEula: selectCurrentEula currentEula: selectCurrentEula
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()), checkUserSession: () => dispatch(checkUserSession()),
setOnline: (isOnline) => dispatch(setOnline(isOnline)) setOnline: (isOnline) => dispatch(setOnline(isOnline)),
}); });
export function App({ bodyshop, checkUserSession, currentUser, online, setOnline, currentEula }) { export function App({bodyshop, checkUserSession, currentUser, online, setOnline, currentEula}) {
const client = useSplitClient().client; const client = useSplitClient().client;
const [listenersAdded, setListenersAdded] = useState(false); const [listenersAdded, setListenersAdded] = useState(false)
const { t } = useTranslation(); const {t} = useTranslation();
useEffect(() => {
if (!navigator.onLine) {
setOnline(false);
}
checkUserSession(); useEffect(() => {
}, [checkUserSession, setOnline]); if (!navigator.onLine) {
setOnline(false);
//const b = Grid.useBreakpoint();
// console.log("Breakpoints:", b);
// Associate event listeners, memoize to prevent multiple listeners being added
useEffect(() => {
const offlineListener = () => {
setOnline(false);
};
const onlineListener = () => {
setOnline(true);
};
if (!listenersAdded) {
console.log("Added events for offline and online");
window.addEventListener("offline", offlineListener);
window.addEventListener("online", onlineListener);
setListenersAdded(true);
}
return () => {
window.removeEventListener("offline", offlineListener);
window.removeEventListener("online", onlineListener);
};
}, [setOnline, listenersAdded]);
useEffect(() => {
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
if (
client.getTreatment("LogRocket_Tracking") === "on" ||
window.location.hostname ===
InstanceRenderMgr({
imex: "beta.imex.online",
rome: "beta.romeonline.io"
})
) {
console.log("LR Start");
LogRocket.init(
InstanceRenderMgr({
imex: "gvfvfw/bodyshopapp",
rome: "rome-online/rome-online",
promanager: "" // TODO: AIO Add in log rocket for promanager instances.
})
);
}
}
}, [bodyshop, client, currentUser.authorized]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")} />;
}
if (!online) {
return (
<Result
status="warning"
title={t("general.labels.nointernet")}
subTitle={t("general.labels.nointernet_sub")}
extra={
<Button type="primary" onClick={() => window.location.reload()}>
{t("general.actions.refresh")}
</Button>
} }
/>
checkUserSession();
}, [checkUserSession, setOnline]);
//const b = Grid.useBreakpoint();
// console.log("Breakpoints:", b);
// Associate event listeners, memoize to prevent multiple listeners being added
useEffect(() => {
const offlineListener = (e) => {
setOnline(false);
}
const onlineListener = (e) => {
setOnline(true);
}
if (!listenersAdded) {
console.log('Added events for offline and online');
window.addEventListener("offline", offlineListener);
window.addEventListener("online", onlineListener);
setListenersAdded(true);
}
return () => {
window.removeEventListener("offline", offlineListener);
window.removeEventListener("online", onlineListener);
}
}, [setOnline, listenersAdded]);
useEffect(() => {
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
if (
client.getTreatment("LogRocket_Tracking") === "on" ||
window.location.hostname === 'beta.imex.online'
) {
console.log("LR Start");
LogRocket.init("gvfvfw/bodyshopapp");
}
}
}, [bodyshop, client, currentUser.authorized]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
}
handleBeta();
if (!online)
return (
<Result
status="warning"
title={t("general.labels.nointernet")}
subTitle={t("general.labels.nointernet_sub")}
extra={
<Button
type="primary"
onClick={() => {
window.location.reload();
}}
>
{t("general.actions.refresh")}
</Button>
}
/>
);
if (currentEula && !currentUser.eulaIsAccepted) {
return <Eula/>
}
// Any route that is not assigned and matched will default to the Landing Page component
return (
<Suspense fallback={<LoadingSpinner message="ImEX Online"/>}>
<Routes>
<Route path="*" element={<ErrorBoundary><LandingPage/></ErrorBoundary>}/>
<Route path="/signin" element={<ErrorBoundary><SignInPage/></ErrorBoundary>}/>
<Route path="/resetpassword" element={<ErrorBoundary><ResetPassword/></ErrorBoundary>}/>
<Route path="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></ErrorBoundary>}/>
<Route path="/manage/*"
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
<Route path="*" element={<ManagePage/>}/>
</Route>
<Route path="/tech/*"
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
<Route path="*" element={<TechPageContainer/>}/>
</Route>
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
<Route path="*" element={<DocumentEditorContainer/>}/>
</Route>
</Routes>
</Suspense>
); );
}
if (currentEula && !currentUser.eulaIsAccepted) {
return <Eula />;
}
// Any route that is not assigned and matched will default to the Landing Page component
return (
<Suspense
fallback={
<LoadingSpinner
message={InstanceRenderMgr({
imex: t("titles.imexonline"),
rome: t("titles.romeonline"),
promanager: t("titles.promanager")
})}
/>
}
>
<ProductFruitsWrapper
currentUser={currentUser}
workspaceCode={InstanceRenderMgr({
imex: null,
rome: "9BkbEseqNqxw8jUH",
promanager: "aoJoEifvezYI0Z0P"
})}
/>
<Routes>
<Route
path="*"
element={
<ErrorBoundary>
<LandingPage />
</ErrorBoundary>
}
/>
<Route
path="/signin"
element={
<ErrorBoundary>
<SignInPage />
</ErrorBoundary>
}
/>
<Route
path="/resetpassword"
element={
<ErrorBoundary>
<ResetPassword />
</ErrorBoundary>
}
/>
<Route
path="/csi/:surveyId"
element={
<ErrorBoundary>
<CsiPage />
</ErrorBoundary>
}
/>
<Route
path="/disclaimer"
element={
<ErrorBoundary>
<DisclaimerPage />
</ErrorBoundary>
}
/>
<Route
path="/mp/:paymentIs"
element={
<ErrorBoundary>
<MobilePaymentContainer />
</ErrorBoundary>
}
/>
<Route
path="/manage/*"
element={
<ErrorBoundary>
<SocketProvider bodyshop={bodyshop}>
<PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider>
</ErrorBoundary>
}
>
<Route path="*" element={<ManagePage />} />
</Route>
<Route
path="/tech/*"
element={
<ErrorBoundary>
<SocketProvider bodyshop={bodyshop}>
<PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider>
</ErrorBoundary>
}
>
<Route path="*" element={<TechPageContainer />} />
</Route>
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
<Route path="*" element={<DocumentEditorContainer />} />
</Route>
</Routes>
</Suspense>
);
} }
export default connect(mapStateToProps, mapDispatchToProps)(App); export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@@ -154,6 +154,7 @@
font-style: italic; font-style: italic;
} }
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td { .ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
background-color: #f4f4f4; background-color: #f4f4f4;
} }
@@ -161,15 +162,3 @@
.rowWithColor > td { .rowWithColor > td {
background-color: var(--bgColor) !important; background-color: var(--bgColor) !important;
} }
.muted-button {
color: lightgray;
border: none;
background: none;
cursor: pointer;
font-size: 16px; /* Adjust as needed */
}
.muted-button:hover {
color: darkgrey;
}

View File

@@ -1,32 +0,0 @@
import React from "react";
import { ProductFruits } from "react-product-fruits";
import PropTypes from "prop-types";
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
return (
workspaceCode &&
currentUser?.authorized === true &&
currentUser?.email && (
<ProductFruits
lifeCycle="unmount"
workspaceCode={workspaceCode}
debug
language="en"
user={{
email: currentUser.email,
username: currentUser.email
}}
/>
)
);
});
export default ProductFruitsWrapper;
ProductFruitsWrapper.propTypes = {
currentUser: PropTypes.shape({
authorized: PropTypes.bool,
email: PropTypes.string
}),
workspaceCode: PropTypes.string
};

View File

@@ -1,8 +1,7 @@
import { defaultsDeep } from "lodash"; import {defaultsDeep} from "lodash";
import { theme } from "antd"; import {theme} from "antd";
import InstanceRenderMgr from "../utils/instanceRenderMgr";
const { defaultAlgorithm, darkAlgorithm } = theme; const {defaultAlgorithm, darkAlgorithm} = theme;
let isDarkMode = false; let isDarkMode = false;
@@ -11,30 +10,21 @@ let isDarkMode = false;
* @type {{components: {Menu: {itemDividerBorderColor: string}}}} * @type {{components: {Menu: {itemDividerBorderColor: string}}}}
*/ */
const defaultTheme = { const defaultTheme = {
components: { components: {
Table: { Table: {
rowHoverBg: "#e7f3ff", rowHoverBg: '#e7f3ff',
rowSelectedBg: "#e6f7ff", rowSelectedBg: '#e6f7ff',
headerSortHoverBg: "transparent" headerSortHoverBg: 'transparent',
},
Menu: {
darkItemHoverBg: '#1677ff',
itemHoverBg: '#1677ff',
horizontalItemHoverBg: '#1677ff',
}
}, },
Menu: { token: {
darkItemHoverBg: "#1890ff", colorPrimary: '#1677ff'
itemHoverBg: "#1890ff",
horizontalItemHoverBg: "#1890ff"
} }
},
token: {
colorPrimary: InstanceRenderMgr({
imex: "#1890ff",
rome: "#326ade",
promanager: "#1d69a6"
}),
colorInfo: InstanceRenderMgr({
imex: "#1890ff",
rome: "#326ade",
promanager: "#1d69a6"
})
}
}; };
/** /**
@@ -42,16 +32,16 @@ const defaultTheme = {
* @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}} * @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}}
*/ */
const devTheme = { const devTheme = {
components: { components: {
Menu: { Menu: {
darkItemHoverBg: "#a51d1d", darkItemHoverBg: '#a51d1d',
itemHoverBg: "#a51d1d", itemHoverBg: '#a51d1d',
horizontalItemHoverBg: "#a51d1d" horizontalItemHoverBg: '#a51d1d',
}
},
token: {
colorPrimary: '#a51d1d'
} }
},
token: {
colorPrimary: "#a51d1d"
}
}; };
/** /**
@@ -60,10 +50,11 @@ const devTheme = {
*/ */
const prodTheme = {}; const prodTheme = {};
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme; const currentTheme = process.env.NODE_ENV === "development" ? devTheme
: prodTheme;
const finaltheme = { const finaltheme = {
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm, algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
...defaultsDeep(currentTheme, defaultTheme) ...defaultsDeep(currentTheme, defaultTheme)
}; }
export default finaltheme; export default finaltheme;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Some files were not shown because too many files have changed in this diff Show More