mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 09:35:56 -05:00
Merge pull request #7952 from scratchfoundation/release/2023-11-15
[Master] release/2023-11-15
This commit is contained in:
commit
6c6b675c44
56 changed files with 2031 additions and 1350 deletions
|
@ -1,197 +0,0 @@
|
|||
version: 2.1
|
||||
|
||||
aliases:
|
||||
- &defaults
|
||||
docker:
|
||||
- image: cimg/node:16.14.2-browsers
|
||||
auth:
|
||||
username: $DOCKERHUB_USERNAME
|
||||
password: $DOCKERHUB_PASSWORD
|
||||
working_directory: ~/repo
|
||||
- &setup
|
||||
name: "setup"
|
||||
command: |
|
||||
npm --production=false ci
|
||||
mkdir ./test/results
|
||||
- &lint
|
||||
name: "run lint tests"
|
||||
command: |
|
||||
npm run test:lint:ci
|
||||
- &build
|
||||
name: "run npm build"
|
||||
command: |
|
||||
WWW_VERSION=${CIRCLE_SHA1:0:5} npm run build
|
||||
- &unit
|
||||
name: "Run unit tests"
|
||||
command: |
|
||||
JEST_JUNIT_OUTPUT_NAME=unit-jest-results.xml npm run test:unit:jest:unit -- --reporters=jest-junit
|
||||
JEST_JUNIT_OUTPUT_NAME=localization-jest-results.xml npm run test:unit:jest:localization -- --reporters=jest-junit
|
||||
npm run test:unit:tap -- --output-file ./test/results/unit-raw.tap
|
||||
npm run test:unit:convertReportToXunit
|
||||
- &setup_python
|
||||
name: "setup python"
|
||||
command: |
|
||||
curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
|
||||
python3 get-pip.py pip==21.0.1
|
||||
pip install s3cmd==2.1.0
|
||||
- &deploy
|
||||
name: "deploy"
|
||||
command: |
|
||||
npm run deploy
|
||||
- &integration
|
||||
name: "integration tests with Jest"
|
||||
command: |
|
||||
JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration:remote -- --reporters=jest-junit
|
||||
- &build_no_deploy
|
||||
<<: *defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
<<: *setup
|
||||
- run:
|
||||
<<: *lint
|
||||
- run:
|
||||
<<: *build
|
||||
- run:
|
||||
<<: *unit
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- &build_and_deploy
|
||||
<<: *defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
<<: *setup
|
||||
- run:
|
||||
<<: *lint
|
||||
- run:
|
||||
<<: *build
|
||||
- run:
|
||||
<<: *unit
|
||||
- run:
|
||||
<<: *setup_python
|
||||
- run:
|
||||
<<: *deploy
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- run:
|
||||
name: Compress Artifacts
|
||||
command: tar -cvzf build.tar build
|
||||
- store_artifacts:
|
||||
path: build.tar
|
||||
- &integration_tests_and_store
|
||||
<<: *defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
<<: *setup
|
||||
- run:
|
||||
<<: *integration
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- &update-translations
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "setup"
|
||||
command: npm --production=false ci
|
||||
- run:
|
||||
name: "run i18n script"
|
||||
command: npm run i18n:push
|
||||
|
||||
# build-test-deploy requires two separately named jobs
|
||||
jobs:
|
||||
build-and-deploy-staging:
|
||||
<<: *build_and_deploy
|
||||
build-and-deploy-production:
|
||||
<<: *build_and_deploy
|
||||
integration-tests:
|
||||
<<: *integration_tests_and_store
|
||||
update-translations:
|
||||
<<: *update-translations
|
||||
build-no-deploy:
|
||||
<<: *build_no_deploy
|
||||
|
||||
workflows:
|
||||
build-test-deploy:
|
||||
jobs:
|
||||
- build-and-deploy-staging:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- beta
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- integration-tests:
|
||||
requires:
|
||||
- build-and-deploy-staging
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- beta
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- build-and-deploy-production:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-production
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- integration-tests:
|
||||
requires:
|
||||
- build-and-deploy-production
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-production
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
Update-translations:
|
||||
triggers:
|
||||
- schedule: # every evening at 7pm EST (8pm EDT, Midnight UTC)
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only: develop
|
||||
jobs:
|
||||
- update-translations:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
build-test-no-deploy:
|
||||
jobs:
|
||||
- build-no-deploy:
|
||||
context:
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- develop
|
||||
- master
|
||||
- beta
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
138
.github/workflows/ci-cd.yml
vendored
Normal file
138
.github/workflows/ci-cd.yml
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
name: CI/CD
|
||||
|
||||
on:
|
||||
pull_request: # Runs whenever a pull request is created or updated
|
||||
push: # Runs whenever a commit is pushed to the repository
|
||||
branches: [master, develop, beta, hotfix/*] # ...on any of these branches
|
||||
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CXX: g++-4.8
|
||||
FASTLY_ACTIVATE_CHANGES: true
|
||||
FASTLY_PURGE_ALL: true
|
||||
NODE_ENV: production
|
||||
SKIP_CLEANUP: true
|
||||
|
||||
jobs:
|
||||
build-and-test-and-maybe-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: >-
|
||||
${{
|
||||
(
|
||||
(github.ref == 'refs/heads/master') && 'production'
|
||||
) ||
|
||||
(
|
||||
(
|
||||
(github.ref == 'refs/heads/develop') ||
|
||||
(github.ref == 'refs/heads/beta') ||
|
||||
startsWith(github.ref, 'refs/heads/hotfix/') ||
|
||||
startsWith(github.ref, 'refs/heads/release/')
|
||||
) && 'staging'
|
||||
) ||
|
||||
''
|
||||
}}
|
||||
env:
|
||||
# SCRATCH_ENV comes from the GitHub Environment
|
||||
# See https://github.com/scratchfoundation/scratch-www/settings/variables/actions
|
||||
SCRATCH_SHOULD_DEPLOY: ${{ vars.SCRATCH_ENV != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'npm'
|
||||
node-version-file: '.nvmrc'
|
||||
- name: info
|
||||
run: |
|
||||
echo "Scratch environment: ${{ vars.SCRATCH_ENV }}"
|
||||
echo "Node version: $(node --version)"
|
||||
echo "NPM version: $(npm --version)"
|
||||
- name: setup
|
||||
run: |
|
||||
npm --production=false ci
|
||||
mkdir -p ./test/results
|
||||
- name: lint
|
||||
run: npm run test:lint:ci
|
||||
- name: build
|
||||
run: WWW_VERSION=${GITHUB_SHA:0:5} npm run build
|
||||
env:
|
||||
# webpack.config.js uses these with `DefinePlugin`
|
||||
API_HOST: ${{ secrets.API_HOST }}
|
||||
RECAPTCHA_SITE_KEY: ${{ secrets.RECAPTCHA_SITE_KEY }}
|
||||
ASSET_HOST: ${{ secrets.ASSET_HOST }}
|
||||
BACKPACK_HOST: ${{ secrets.BACKPACK_HOST }}
|
||||
CLOUDDATA_HOST: ${{ secrets.CLOUDDATA_HOST }}
|
||||
PROJECT_HOST: ${{ secrets.PROJECT_HOST }}
|
||||
STATIC_HOST: ${{ secrets.STATIC_HOST }}
|
||||
SCRATCH_ENV: ${{ vars.SCRATCH_ENV }}
|
||||
|
||||
# used by src/template-config.js
|
||||
GTM_ID: ${{ secrets.GTM_ID }}
|
||||
GTM_ENV_AUTH: ${{ secrets.GTM_ENV_AUTH }}
|
||||
- name: unit tests
|
||||
run: |
|
||||
JEST_JUNIT_OUTPUT_NAME=unit-jest-results.xml npm run test:unit:jest:unit -- --reporters=jest-junit
|
||||
JEST_JUNIT_OUTPUT_NAME=localization-jest-results.xml npm run test:unit:jest:localization -- --reporters=jest-junit
|
||||
npm run test:unit:tap -- --output-file ./test/results/unit-raw.tap
|
||||
npm run test:unit:convertReportToXunit
|
||||
- name: setup Python
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: |
|
||||
curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
|
||||
python3 get-pip.py pip==21.0.1
|
||||
pip install s3cmd==2.3.0
|
||||
- name: deploy
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: npm run deploy
|
||||
env:
|
||||
S3_LOCAL_DIR: build
|
||||
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
FASTLY_API_KEY: ${{ secrets.FASTLY_API_KEY }}
|
||||
FASTLY_SERVICE_ID: ${{ secrets.FASTLY_SERVICE_ID }}
|
||||
SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS: ${{ secrets.SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS }} # TODO: rename or replace
|
||||
SLACK_WEBHOOK_ENGINEERING: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }}
|
||||
SLACK_WEBHOOK_MODS: ${{ secrets.SLACK_WEBHOOK_MODS }}
|
||||
- name: integration tests
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: |
|
||||
# if the health test fails, there's no point in trying to run the integration tests
|
||||
npm run test:health
|
||||
# health test succeeded, so proceed with integration tests
|
||||
JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration -- --reporters=jest-junit
|
||||
env:
|
||||
ROOT_URL: ${{ secrets.ROOT_URL }}
|
||||
|
||||
# test/integration-legacy/selenium-helpers.js
|
||||
CI: "true"
|
||||
CIRCLECI: "true" # TODO
|
||||
CIRCLE_BUILD_NUM: ${{ github.run_id }} # TODO
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
|
||||
SMOKE_REMOTE: "true" # use Sauce Labs
|
||||
|
||||
# test/integration/*
|
||||
SMOKE_USERNAME: ${{ secrets.SMOKE_USERNAME }}
|
||||
SMOKE_PASSWORD: ${{ secrets.SMOKE_PASSWORD }}
|
||||
COMMENT_PROJECT_ID: ${{ secrets.COMMENT_PROJECT_ID }}
|
||||
COMMENT_STUDIO_ID: ${{ secrets.COMMENT_STUDIO_ID }}
|
||||
UNOWNED_SHARED_PROJECT_ID: ${{ secrets.UNOWNED_SHARED_PROJECT_ID }}
|
||||
OWNED_SHARED_PROJECT_ID: ${{ secrets.OWNED_SHARED_PROJECT_ID }}
|
||||
OWNED_UNSHARED_PROJECT_ID: ${{ secrets.OWNED_UNSHARED_PROJECT_ID }}
|
||||
UNOWNED_UNSHARED_PROJECT_ID: ${{ secrets.UNOWNED_UNSHARED_PROJECT_ID }}
|
||||
UNOWNED_SHARED_SCRATCH2_PROJECT_ID: ${{ secrets.UNOWNED_SHARED_SCRATCH2_PROJECT_ID }}
|
||||
OWNED_UNSHARED_SCRATCH2_PROJECT_ID: ${{ secrets.OWNED_UNSHARED_SCRATCH2_PROJECT_ID }}
|
||||
TEST_STUDIO_ID: ${{ secrets.TEST_STUDIO_ID }}
|
||||
RATE_LIMIT_CHECK: ${{ secrets.RATE_LIMIT_CHECK }}
|
||||
- name: compress artifact
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: tar -czvf build.tgz build
|
||||
- name: upload artifact
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: build.tgz
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
v16
|
425
package-lock.json
generated
425
package-lock.json
generated
|
@ -21,7 +21,7 @@
|
|||
"react-twitter-embed": "^3.0.3",
|
||||
"react-use": "^17.3.1",
|
||||
"scratch-parser": "5.1.1",
|
||||
"scratch-storage": "2.2.1"
|
||||
"scratch-storage": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/intl-datetimeformat": "6.4.3",
|
||||
|
@ -53,6 +53,7 @@
|
|||
"enzyme-adapter-react-16": "1.14.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-scratch": "7.0.0",
|
||||
"eslint-plugin-jest": "24.7.0",
|
||||
"eslint-plugin-json": "2.0.1",
|
||||
"eslint-plugin-react": "7.14.2",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
|
@ -79,7 +80,7 @@
|
|||
"minilog": "2.0.8",
|
||||
"pako": "0.2.8",
|
||||
"plotly.js": "1.47.4",
|
||||
"postcss": "8.4.6",
|
||||
"postcss": "8.4.31",
|
||||
"postcss-loader": "4.2.0",
|
||||
"prop-types": "15.6.0",
|
||||
"query-string": "5.1.1",
|
||||
|
@ -101,8 +102,8 @@
|
|||
"regenerator-runtime": "0.13.9",
|
||||
"sass": "1.49.7",
|
||||
"sass-loader": "10.4.1",
|
||||
"scratch-gui": "3.0.16",
|
||||
"scratch-l10n": "3.16.20231003032155",
|
||||
"scratch-gui": "3.2.37",
|
||||
"scratch-l10n": "3.16.20231024152916",
|
||||
"selenium-webdriver": "4.1.0",
|
||||
"slick-carousel": "1.6.0",
|
||||
"style-loader": "0.12.3",
|
||||
|
@ -1888,9 +1889,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@scratch/paper": {
|
||||
"version": "0.11.20200728195508",
|
||||
"resolved": "https://registry.npmjs.org/@scratch/paper/-/paper-0.11.20200728195508.tgz",
|
||||
"integrity": "sha512-cphYw/y/l36UJ8fv/LXyK+lHlxMXtoydJUsgA4u5QnaUaSZYepuSHik6PewJGT4qvaPwT5ImvHWwX2kElWXvoQ==",
|
||||
"version": "0.11.20221201200345",
|
||||
"resolved": "https://registry.npmjs.org/@scratch/paper/-/paper-0.11.20221201200345.tgz",
|
||||
"integrity": "sha512-I3BZNrHeaQJt2H6TD7HGsuBKJPDDF/BIDOaRvnN7Gj/QBRvpSaRK8JAmzcrRHZ+AqNtKrG50eOkS/acMjTw3rw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
|
@ -2073,6 +2074,224 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/experimental-utils": {
|
||||
"version": "4.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz",
|
||||
"integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.7",
|
||||
"@typescript-eslint/scope-manager": "4.33.0",
|
||||
"@typescript-eslint/types": "4.33.0",
|
||||
"@typescript-eslint/typescript-estree": "4.33.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.12.0 || >=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mysticatea"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "4.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz",
|
||||
"integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "4.33.0",
|
||||
"@typescript-eslint/visitor-keys": "4.33.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "4.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz",
|
||||
"integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "4.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz",
|
||||
"integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "4.33.0",
|
||||
"@typescript-eslint/visitor-keys": "4.33.0",
|
||||
"debug": "^4.3.1",
|
||||
"globby": "^11.0.3",
|
||||
"is-glob": "^4.0.1",
|
||||
"semver": "^7.3.5",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.12.0 || >=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "4.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz",
|
||||
"integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "4.33.0",
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vernier/godirect": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vernier/godirect/-/godirect-1.5.0.tgz",
|
||||
|
@ -8550,6 +8769,27 @@
|
|||
"eslint": ">=5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jest": {
|
||||
"version": "24.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz",
|
||||
"integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/experimental-utils": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": ">= 4",
|
||||
"eslint": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-json": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-json/-/eslint-plugin-json-2.0.1.tgz",
|
||||
|
@ -8874,16 +9114,24 @@
|
|||
}
|
||||
},
|
||||
"node_modules/esrecurse": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
|
||||
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"dependencies": {
|
||||
"estraverse": "^4.1.0"
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esrecurse/node_modules/estraverse": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estraverse": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
|
||||
|
@ -17409,10 +17657,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
|
||||
"integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
|
@ -19157,21 +19411,31 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
|
||||
"integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.2.0",
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-import": {
|
||||
|
@ -23293,9 +23557,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-audio": {
|
||||
"version": "0.1.0-prerelease.20221123180128",
|
||||
"resolved": "https://registry.npmjs.org/scratch-audio/-/scratch-audio-0.1.0-prerelease.20221123180128.tgz",
|
||||
"integrity": "sha512-yj7mSkHmKxQVdBalAew1UjfTthOoTs3pejhh8Tl65KcC3X4+fLbmY1F3woYSrZbXxPtYUZTKyrcwlCh8r1DQjg==",
|
||||
"version": "0.1.0-prerelease.20231013154641",
|
||||
"resolved": "https://registry.npmjs.org/scratch-audio/-/scratch-audio-0.1.0-prerelease.20231013154641.tgz",
|
||||
"integrity": "sha512-QHpN38xjm8v5KTTXDIfrW0MKPYw+R7y9gt5r6tzmah3SW8TPnk82w/XZ8zwgrVJcuzv0lu7/CTZUJv4XxK11Jw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"audio-context": "1.0.1",
|
||||
|
@ -23319,21 +23583,39 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-blocks": {
|
||||
"version": "0.2.0-prerelease.20231003094735",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.2.0-prerelease.20231003094735.tgz",
|
||||
"integrity": "sha512-TgaVZpZVVXcKCyvBZUnd+E2y6oHuKe6hNQsn5aUf1C9B+HWT+h7bUQRRfbg50n9VWrrAzBT0bGGCsSLhB7Dfnw==",
|
||||
"version": "0.2.0-prerelease.20231013132110",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.2.0-prerelease.20231013132110.tgz",
|
||||
"integrity": "sha512-3ShqtHYHM0LmGcEb0wXNvIsPKI5iauEghzdJeXxxDUOElq1XG6I6iO1+3j5BUxD3HwfuG3LlG9VpfyKz67vnJQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"exports-loader": "0.7.0",
|
||||
"google-closure-library": "20190301.0.0",
|
||||
"imports-loader": "0.8.0",
|
||||
"scratch-l10n": "3.16.20231003032155"
|
||||
"scratch-l10n": "3.16.20231013034330"
|
||||
}
|
||||
},
|
||||
"node_modules/scratch-blocks/node_modules/scratch-l10n": {
|
||||
"version": "3.16.20231013034330",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.16.20231013034330.tgz",
|
||||
"integrity": "sha512-zHiSFijBt5Dq7ceLIxEhXvgDn997r3HVXkGjMSQmyA4oyzYwCOVvzoxtu5U2UrxNEcJy7beFLfIn5GrV66/PvQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
"@babel/core": "^7.1.2",
|
||||
"@transifex/api": "4.2.5",
|
||||
"babel-plugin-react-intl": "^3.0.1",
|
||||
"download": "^8.0.0",
|
||||
"transifex": "1.6.6"
|
||||
},
|
||||
"bin": {
|
||||
"build-i18n-src": "scripts/build-i18n-src.js",
|
||||
"tx-push-src": "scripts/tx-push-src.js"
|
||||
}
|
||||
},
|
||||
"node_modules/scratch-gui": {
|
||||
"version": "3.0.16",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-3.0.16.tgz",
|
||||
"integrity": "sha512-AkI9vkxmSs/p8gslG6OidxNT5oMXmDaH/j4mLC2WzrVcCE2hWVG1NfxjZE+dKgW6e13JBBIz63sCDCTT+N6iqg==",
|
||||
"version": "3.2.37",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-3.2.37.tgz",
|
||||
"integrity": "sha512-WbSrLycsE87/TqAtbbCW6F/ZgXV+AyDUTBs9/Y9KTzc0FURwDVTgdilnWY7pgj3sfHaRtCpLRTFAKjM5heRi8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@microbit/microbit-universal-hex": "0.2.2",
|
||||
|
@ -23387,15 +23669,15 @@
|
|||
"react-virtualized": "9.20.1",
|
||||
"redux": "3.7.2",
|
||||
"redux-throttle": "0.1.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20221123180128",
|
||||
"scratch-blocks": "0.2.0-prerelease.20231003094735",
|
||||
"scratch-l10n": "3.16.20231003032155",
|
||||
"scratch-paint": "2.1.21",
|
||||
"scratch-render": "0.1.0-prerelease.20230913153807",
|
||||
"scratch-render-fonts": "1.0.0-prerelease.20221102164332",
|
||||
"scratch-storage": "2.2.1",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20230710144521",
|
||||
"scratch-vm": "2.0.5",
|
||||
"scratch-audio": "0.1.0-prerelease.20231013154641",
|
||||
"scratch-blocks": "0.2.0-prerelease.20231013132110",
|
||||
"scratch-l10n": "3.16.20231024152916",
|
||||
"scratch-paint": "2.1.34",
|
||||
"scratch-render": "0.1.0-prerelease.20231018030724",
|
||||
"scratch-render-fonts": "1.0.0-prerelease.20231017225105",
|
||||
"scratch-storage": "2.3.1",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20231013154115",
|
||||
"scratch-vm": "2.1.14",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
"text-encoding": "0.7.0",
|
||||
|
@ -23715,12 +23997,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-gui/node_modules/scratch-paint": {
|
||||
"version": "2.1.21",
|
||||
"resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-2.1.21.tgz",
|
||||
"integrity": "sha512-9vjWDZEAjTlnWN1TWKOvH3DAPcznY4+EnPiUpviGVhpeFvlye57Hrj/OorYPmJ65lwNg2NeY6evKU2mUGRJDXQ==",
|
||||
"version": "2.1.34",
|
||||
"resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-2.1.34.tgz",
|
||||
"integrity": "sha512-SoyaJF4HRJb3Rats5owRg8NV0zdxIoec0z4VR7177WeN1SoVHXvEPrHAqr3A1JSkJ6aG2ssoBjdppkxebyBtmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@scratch/paper": "0.11.20200728195508",
|
||||
"@scratch/paper": "0.11.20221201200345",
|
||||
"classnames": "2.2.5",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.bindall": "4.4.0",
|
||||
|
@ -23803,9 +24085,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-l10n": {
|
||||
"version": "3.16.20231003032155",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.16.20231003032155.tgz",
|
||||
"integrity": "sha512-tDqWskv/CJGzLrFr0pUVtHoima+BSYKpLf5A0UzAwMInOgQ0yzjZiyDsuTKpGpkiiyVgns6m7QsBHvMlCv5uTQ==",
|
||||
"version": "3.16.20231024152916",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.16.20231024152916.tgz",
|
||||
"integrity": "sha512-dg1am1tHdRBshQvEYMgcveouVsNQEIW1sd4giAQIN57rE/PKBgER3R0Zc3fqZxymd1JL2zIpkYB8bkDZRFadWA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
|
@ -23844,9 +24126,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-render": {
|
||||
"version": "0.1.0-prerelease.20230913153807",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render/-/scratch-render-0.1.0-prerelease.20230913153807.tgz",
|
||||
"integrity": "sha512-A2bGt7/92VEoI9n64SHtz8jYc/6EEqfBLzVAYrOom4MOgu1aYYOw6eBi6sGkDdaCIHZIdNzS7k4L3lxDXYiHHw==",
|
||||
"version": "0.1.0-prerelease.20231018030724",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render/-/scratch-render-0.1.0-prerelease.20231018030724.tgz",
|
||||
"integrity": "sha512-XNn0tMEqOOy1UlbGpvw8qwhqEmYotB1uVe5RuKp7Om5QWNgKh3x3lyyQsgBOcGGR1YSrfFFUj/hagBm/bipuXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"grapheme-breaker": "0.3.2",
|
||||
|
@ -23856,7 +24138,7 @@
|
|||
"minilog": "3.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"scratch-storage": "^1.0.0",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20230710144521",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20231013154115",
|
||||
"twgl.js": "4.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -23864,9 +24146,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-render-fonts": {
|
||||
"version": "1.0.0-prerelease.20221102164332",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render-fonts/-/scratch-render-fonts-1.0.0-prerelease.20221102164332.tgz",
|
||||
"integrity": "sha512-22MbRDGUSArVEoHatg5rt7f/H0wWhMrcyN6HD0OQJeDqdlO3qSSX9/qvdzNJGYWwZkhrdJWcI5JGD1YuJfefmw==",
|
||||
"version": "1.0.0-prerelease.20231017225105",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render-fonts/-/scratch-render-fonts-1.0.0-prerelease.20231017225105.tgz",
|
||||
"integrity": "sha512-Ske5+x9OzfT7wf+eAnMQHutCzyle1er3ncywPMLPC6UDjKrlHYUFVNeTR3vJb3EoMdBNPl/yVKuJItGPQoUugg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"base64-loader": "1.0.0"
|
||||
|
@ -23934,9 +24216,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-storage": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-2.2.1.tgz",
|
||||
"integrity": "sha512-qogGcWBXqKUHgfvSgyUkos4fuj7z+SDDHBVlT3NNC4gtZgw4dq+USwHjKXCwtRs6BN/joI7+LafFJtSLii6G/w==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-2.3.1.tgz",
|
||||
"integrity": "sha512-kBxJLFGQsimP35YHig+8op5V9+FLfO7mXbhSLj4712dyHTXvyMKYY8GAf7KT283kQrUpDApehJU4Jx5PXSjLXQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.21.0",
|
||||
"arraybuffer-loader": "^1.0.3",
|
||||
|
@ -23967,9 +24249,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scratch-svg-renderer": {
|
||||
"version": "0.2.0-prerelease.20230710144521",
|
||||
"resolved": "https://registry.npmjs.org/scratch-svg-renderer/-/scratch-svg-renderer-0.2.0-prerelease.20230710144521.tgz",
|
||||
"integrity": "sha512-T+jmJGZWeBEAtIsV+6WpAzglANRaUJVxQLIKoD68VhD142l6XBf5WwHJiJkK9vysp6H24zchwTZwq3R34NF14Q==",
|
||||
"version": "0.2.0-prerelease.20231013154115",
|
||||
"resolved": "https://registry.npmjs.org/scratch-svg-renderer/-/scratch-svg-renderer-0.2.0-prerelease.20231013154115.tgz",
|
||||
"integrity": "sha512-z487RJ9bxGsTeaoAhzdlKb/K6SdeiaL8fVhRK1JYZNDzJYbrxxm3N3PVN79tHBEY0x/gFizpCi1CNNNMlyVHAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"base64-js": "1.2.1",
|
||||
|
@ -24006,9 +24288,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/scratch-vm": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-2.0.5.tgz",
|
||||
"integrity": "sha512-g1PVPsq4QWF7RLDLfcNeweTTpzfqlix8YVMgeKWItvpZtl4lpPaqfAoqPYQpPRduIP0fFIi4GT6OV9dsZMOBqw==",
|
||||
"version": "2.1.14",
|
||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-2.1.14.tgz",
|
||||
"integrity": "sha512-tkoCYtJediovJN3CSd667/NtU3RLDFnv2vpfb6stwhXRUAfdjcOSmEB2QsdwhquED277gHmKTCPFM1hOJhqipQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vernier/godirect": "1.5.0",
|
||||
|
@ -29309,6 +29591,21 @@
|
|||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
|
||||
},
|
||||
"node_modules/tsutils": {
|
||||
"version": "3.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
|
||||
"integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^1.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
|
||||
}
|
||||
},
|
||||
"node_modules/tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||
|
|
11
package.json
11
package.json
|
@ -7,8 +7,8 @@
|
|||
"test": "npm run test:lint && npm run build && npm run test:unit",
|
||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
||||
"test:health": "jest ./test/health/*.test.js",
|
||||
"test:integration": "jest ./test/integration/*.test.js --reporters=default --maxWorkers=5",
|
||||
"test:integration:remote": "SMOKE_REMOTE=true jest ./test/integration/*.test.js --reporters=default --maxWorkers=5",
|
||||
"test:unit": "npm run test:unit:jest && npm run test:unit:tap",
|
||||
"test:unit:jest": "npm run test:unit:jest:unit && npm run test:unit:jest:localization",
|
||||
"test:unit:jest:unit": "jest ./test/unit/ --reporters=default",
|
||||
|
@ -56,7 +56,7 @@
|
|||
"react-twitter-embed": "^3.0.3",
|
||||
"react-use": "^17.3.1",
|
||||
"scratch-parser": "5.1.1",
|
||||
"scratch-storage": "2.2.1"
|
||||
"scratch-storage": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/intl-datetimeformat": "6.4.3",
|
||||
|
@ -88,6 +88,7 @@
|
|||
"enzyme-adapter-react-16": "1.14.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-scratch": "7.0.0",
|
||||
"eslint-plugin-jest": "24.7.0",
|
||||
"eslint-plugin-json": "2.0.1",
|
||||
"eslint-plugin-react": "7.14.2",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
|
@ -114,7 +115,7 @@
|
|||
"minilog": "2.0.8",
|
||||
"pako": "0.2.8",
|
||||
"plotly.js": "1.47.4",
|
||||
"postcss": "8.4.6",
|
||||
"postcss": "8.4.31",
|
||||
"postcss-loader": "4.2.0",
|
||||
"prop-types": "15.6.0",
|
||||
"query-string": "5.1.1",
|
||||
|
@ -136,8 +137,8 @@
|
|||
"regenerator-runtime": "0.13.9",
|
||||
"sass": "1.49.7",
|
||||
"sass-loader": "10.4.1",
|
||||
"scratch-gui": "3.0.16",
|
||||
"scratch-l10n": "3.16.20231003032155",
|
||||
"scratch-gui": "3.2.37",
|
||||
"scratch-l10n": "3.16.20231024152916",
|
||||
"selenium-webdriver": "4.1.0",
|
||||
"slick-carousel": "1.6.0",
|
||||
"style-loader": "0.12.3",
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
module.exports = {
|
||||
extends: ['scratch/react'],
|
||||
extends: ['scratch/react', 'scratch/es6', 'plugin:jest/recommended'],
|
||||
env: {
|
||||
jest: true
|
||||
},
|
||||
rules: {
|
||||
'jest/no-done-callback': 'off', // TODO: convert callback-based tests to async/await
|
||||
'no-confusing-arrow': [
|
||||
'error',
|
||||
{
|
||||
allowParens: true
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
51
test/health/server-health.test.js
Normal file
51
test/health/server-health.test.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
// this basic server health check is meant to be run before integration tests
|
||||
// it should be run with the same environment variables as the integration tests
|
||||
// and operate in the same way as the integration tests
|
||||
|
||||
const SeleniumHelper = require('../integration/selenium-helpers.js');
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || (() => {
|
||||
const ROOT_URL_DEFAULT = 'https://scratch.ly';
|
||||
console.warn(`ROOT_URL not set, defaulting to ${ROOT_URL_DEFAULT}`);
|
||||
return ROOT_URL_DEFAULT;
|
||||
})();
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
describe('www server health check', () => {
|
||||
/** @type {import('selenium-webdriver').ThenableWebDriver} */
|
||||
let driver;
|
||||
|
||||
/** @type {SeleniumHelper} */
|
||||
let seleniumHelper;
|
||||
|
||||
beforeAll(() => {
|
||||
seleniumHelper = new SeleniumHelper();
|
||||
driver = seleniumHelper.buildDriver('www server health check');
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('server is healthy', async () => {
|
||||
const healthUrl = new URL('health/', rootUrl);
|
||||
await driver.get(healthUrl.toString());
|
||||
|
||||
// Note: driver.getPageSource() will return the pretty HTML form of the JSON
|
||||
const pageText = await driver.executeScript('return document.body.innerText');
|
||||
|
||||
let healthObject;
|
||||
let serverReturnedValidJson = false;
|
||||
|
||||
try {
|
||||
healthObject = JSON.parse(pageText);
|
||||
serverReturnedValidJson = true;
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
expect(serverReturnedValidJson).toBe(true);
|
||||
expect(healthObject).toHaveProperty('healthy', true);
|
||||
});
|
||||
});
|
|
@ -7,19 +7,17 @@ import {createIntl, IntlProvider} from 'react-intl';
|
|||
import {mount, shallow} from 'enzyme';
|
||||
import intlShape from '../../src/lib/intl-shape';
|
||||
|
||||
const shallowWithIntl = (node, {context} = {}) => {
|
||||
return shallow(
|
||||
node,
|
||||
{
|
||||
context: Object.assign({}, context),
|
||||
wrappingComponent: IntlProvider,
|
||||
wrappingComponentProps: {
|
||||
locale: 'en',
|
||||
messages: {}
|
||||
}
|
||||
const shallowWithIntl = (node, {context} = {}) => shallow(
|
||||
node,
|
||||
{
|
||||
context: Object.assign({}, context),
|
||||
wrappingComponent: IntlProvider,
|
||||
wrappingComponentProps: {
|
||||
locale: 'en',
|
||||
messages: {}
|
||||
}
|
||||
).dive();
|
||||
};
|
||||
}
|
||||
).dive();
|
||||
|
||||
const mountWithIntl = (node, {context, childContextTypes} = {}) => {
|
||||
const intl = createIntl({locale: 'en', messages: {}});
|
||||
|
|
|
@ -36,8 +36,8 @@ class SeleniumHelper {
|
|||
if (remote === 'true'){
|
||||
let nameToUse;
|
||||
if (ci === 'true'){
|
||||
let ciName = usingCircle ? 'circleCi ' : 'unknown ';
|
||||
nameToUse = ciName + buildID + ' : ' + name;
|
||||
const ciName = usingCircle ? 'circleCi ' : 'unknown ';
|
||||
nameToUse = `${ciName + buildID} : ${name}`;
|
||||
} else {
|
||||
nameToUse = name;
|
||||
}
|
||||
|
@ -50,14 +50,14 @@ class SeleniumHelper {
|
|||
|
||||
getDriver () {
|
||||
const chromeCapabilities = webdriver.Capabilities.chrome();
|
||||
let args = [];
|
||||
const args = [];
|
||||
if (headless) {
|
||||
args.push('--headless');
|
||||
args.push('window-size=1024,1680');
|
||||
args.push('--no-sandbox');
|
||||
}
|
||||
chromeCapabilities.set('chromeOptions', {args});
|
||||
let driver = new webdriver.Builder()
|
||||
const driver = new webdriver.Builder()
|
||||
.forBrowser('chrome')
|
||||
.withCapabilities(chromeCapabilities)
|
||||
.build();
|
||||
|
@ -67,12 +67,12 @@ class SeleniumHelper {
|
|||
getSauceDriver (username, accessKey, name) {
|
||||
// Driver configs can be generated with the Sauce Platform Configurator
|
||||
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
|
||||
let driverConfig = {
|
||||
const driverConfig = {
|
||||
browserName: 'chrome',
|
||||
platform: 'macOS 10.14',
|
||||
version: '76.0'
|
||||
};
|
||||
var driver = new webdriver.Builder()
|
||||
const driver = new webdriver.Builder()
|
||||
.withCapabilities({
|
||||
browserName: driverConfig.browserName,
|
||||
platform: driverConfig.platform,
|
||||
|
@ -128,13 +128,13 @@ class SeleniumHelper {
|
|||
}
|
||||
|
||||
dragFromXpathToXpath (startXpath, endXpath) {
|
||||
return this.findByXpath(startXpath).then(startEl => {
|
||||
return this.findByXpath(endXpath).then(endEl => {
|
||||
return this.driver.actions()
|
||||
return this.findByXpath(startXpath).then(startEl =>
|
||||
this.findByXpath(endXpath).then(endEl =>
|
||||
this.driver.actions()
|
||||
.dragAndDrop(startEl, endEl)
|
||||
.perform();
|
||||
});
|
||||
});
|
||||
.perform()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
urlMatches (regex) {
|
||||
|
@ -145,24 +145,22 @@ class SeleniumHelper {
|
|||
return this.driver.manage()
|
||||
.logs()
|
||||
.get('browser')
|
||||
.then((entries) => {
|
||||
return entries.filter((entry) => {
|
||||
const message = entry.message;
|
||||
for (let i = 0; i < whitelist.length; i++) {
|
||||
if (message.indexOf(whitelist[i]) !== -1) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
|
||||
return false;
|
||||
} else if (entry.level !== 'SEVERE') {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring non-SEVERE entry: ' + message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
.then(entries => entries.filter(entry => {
|
||||
const message = entry.message;
|
||||
for (let i = 0; i < whitelist.length; i++) {
|
||||
if (message.indexOf(whitelist[i]) !== -1) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
|
||||
return false;
|
||||
} else if (entry.level !== 'SEVERE') {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring non-SEVERE entry: ' + message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ module.exports.constants = {
|
|||
};
|
||||
|
||||
module.exports.fillUsernameSlide = function (driver, seleniumWebdriver) {
|
||||
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
var usernamePromise = usernameInput.sendKeys('clipspringer');
|
||||
var passwordPromise = passwordInput.sendKeys('educators');
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
return Promise.all([usernamePromise, passwordPromise]).then(function () { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(function () {
|
||||
const passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
const usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
const usernamePromise = usernameInput.sendKeys('clipspringer');
|
||||
const passwordPromise = passwordInput.sendKeys('educators');
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
return Promise.all([usernamePromise, passwordPromise]).then(() => { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(() => {
|
||||
driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.className('demographics-step')));
|
||||
});
|
||||
|
@ -23,13 +23,13 @@ module.exports.fillUsernameSlide = function (driver, seleniumWebdriver) {
|
|||
};
|
||||
|
||||
module.exports.fillDemographicsSlide = function (driver, seleniumWebdriver) {
|
||||
var clickMaleInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@value="male"' +
|
||||
const clickMaleInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@value="male"' +
|
||||
'and @type="radio"]')).click();
|
||||
var selectCountry = driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]' +
|
||||
const selectCountry = driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]' +
|
||||
'/option[@value="us"]')).click();
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
return Promise.all([clickMaleInput, selectCountry]).then(function () { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(function () {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
return Promise.all([clickMaleInput, selectCountry]).then(() => { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(() => {
|
||||
driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.className('name-step')));
|
||||
});
|
||||
|
@ -37,11 +37,11 @@ module.exports.fillDemographicsSlide = function (driver, seleniumWebdriver) {
|
|||
};
|
||||
|
||||
module.exports.fillNameSlide = function (driver, seleniumWebdriver) {
|
||||
var firstNamePromise = driver.findElement(seleniumWebdriver.By.name('user.name.first')).sendKeys('first');
|
||||
var lastNamePromise = driver.findElement(seleniumWebdriver.By.name('user.name.last')).sendKeys('surname');
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
return Promise.all([firstNamePromise, lastNamePromise]).then(function () { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(function () {
|
||||
const firstNamePromise = driver.findElement(seleniumWebdriver.By.name('user.name.first')).sendKeys('first');
|
||||
const lastNamePromise = driver.findElement(seleniumWebdriver.By.name('user.name.last')).sendKeys('surname');
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
return Promise.all([firstNamePromise, lastNamePromise]).then(() => { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(() => {
|
||||
driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.className('phone-step')));
|
||||
});
|
||||
|
@ -49,13 +49,13 @@ module.exports.fillNameSlide = function (driver, seleniumWebdriver) {
|
|||
};
|
||||
|
||||
module.exports.fillPhoneSlide = function (driver, seleniumWebdriver) {
|
||||
var phoneInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="tel"]'));
|
||||
var consentCheckbox = driver.findElement(seleniumWebdriver.By.name('phoneConsent'));
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
var phoneNumberPromise = phoneInput.sendKeys('6172535960');
|
||||
var consentPromise = consentCheckbox.click();
|
||||
return Promise.all([phoneNumberPromise, consentPromise]).then(function () { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(function () {
|
||||
const phoneInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="tel"]'));
|
||||
const consentCheckbox = driver.findElement(seleniumWebdriver.By.name('phoneConsent'));
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
const phoneNumberPromise = phoneInput.sendKeys('6172535960');
|
||||
const consentPromise = consentCheckbox.click();
|
||||
return Promise.all([phoneNumberPromise, consentPromise]).then(() => { // eslint-disable-line no-undef
|
||||
nextStepButton.click().then(() => {
|
||||
driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.className('organization-step')));
|
||||
});
|
||||
|
@ -63,16 +63,16 @@ module.exports.fillPhoneSlide = function (driver, seleniumWebdriver) {
|
|||
};
|
||||
|
||||
module.exports.fillOrganizationSlide = function (driver, seleniumWebdriver) {
|
||||
var organizationInput = driver.findElement(seleniumWebdriver.By.name('organization.name'));
|
||||
var titleInput = driver.findElement(seleniumWebdriver.By.name('organization.title'));
|
||||
var typeCheckbox = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="checkbox" and @value="3"]'));
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
var organizationPromise = organizationInput.sendKeys('MIT Media Lab');
|
||||
var titlePromise = titleInput.sendKeys('Software Developer');
|
||||
var typePromise = typeCheckbox.click();
|
||||
const organizationInput = driver.findElement(seleniumWebdriver.By.name('organization.name'));
|
||||
const titleInput = driver.findElement(seleniumWebdriver.By.name('organization.title'));
|
||||
const typeCheckbox = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="checkbox" and @value="3"]'));
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
const organizationPromise = organizationInput.sendKeys('MIT Media Lab');
|
||||
const titlePromise = titleInput.sendKeys('Software Developer');
|
||||
const typePromise = typeCheckbox.click();
|
||||
return Promise.all([organizationPromise, titlePromise, typePromise]) // eslint-disable-line no-undef
|
||||
.then(function () {
|
||||
nextStepButton.click().then(function () {
|
||||
.then(() => {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.className('address-step')));
|
||||
});
|
||||
|
@ -80,18 +80,18 @@ module.exports.fillOrganizationSlide = function (driver, seleniumWebdriver) {
|
|||
};
|
||||
|
||||
module.exports.fillAddressSlide = function (driver, seleniumWebdriver) {
|
||||
var addressInput = driver.findElement(seleniumWebdriver.By.name('address.line1'));
|
||||
var cityInput = driver.findElement(seleniumWebdriver.By.name('address.city'));
|
||||
var zipCodeInput = driver.findElement(seleniumWebdriver.By.name('address.zip'));
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
var addressPromise = addressInput.sendKeys('77 Massachusetts Avenue, E14/E15');
|
||||
var cityPromise = cityInput.sendKeys('Cambridge');
|
||||
var statePromise = driver.findElement(seleniumWebdriver.By.xpath('//select[@name="address.state"]' +
|
||||
const addressInput = driver.findElement(seleniumWebdriver.By.name('address.line1'));
|
||||
const cityInput = driver.findElement(seleniumWebdriver.By.name('address.city'));
|
||||
const zipCodeInput = driver.findElement(seleniumWebdriver.By.name('address.zip'));
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
|
||||
const addressPromise = addressInput.sendKeys('77 Massachusetts Avenue, E14/E15');
|
||||
const cityPromise = cityInput.sendKeys('Cambridge');
|
||||
const statePromise = driver.findElement(seleniumWebdriver.By.xpath('//select[@name="address.state"]' +
|
||||
'/option[@value="us-ma"]')).click();
|
||||
var zipPromise = zipCodeInput.sendKeys('02139');
|
||||
const zipPromise = zipCodeInput.sendKeys('02139');
|
||||
return Promise.all([addressPromise, cityPromise, statePromise, zipPromise]) // eslint-disable-line no-undef
|
||||
.then(function () {
|
||||
nextStepButton.click().then(function () {
|
||||
.then(() => {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.className('usescratch-step')));
|
||||
});
|
||||
|
|
|
@ -4,27 +4,27 @@
|
|||
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
|
||||
*/
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
var utils = require('./teacher_registration_utils.js');
|
||||
var constants = utils.constants;
|
||||
const utils = require('./teacher_registration_utils.js');
|
||||
const constants = utils.constants;
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(2);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
driver.get(rootUrl + '/educators/register');
|
||||
driver.get(`${rootUrl}/educators/register`);
|
||||
return utils.fillUsernameSlide(driver, seleniumWebdriver)
|
||||
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
|
@ -33,26 +33,26 @@ tap.beforeEach(function () {
|
|||
});
|
||||
|
||||
// Selects Vatican City as the country, and checks that the state dropdown disappears
|
||||
tap.test('checkStateDropdownOnlyPresentWhenNeeded', function (t) {
|
||||
tap.test('checkStateDropdownOnlyPresentWhenNeeded', t => {
|
||||
driver.findElement(seleniumWebdriver.By.xpath('//select[@name="address.country"]' +
|
||||
'/option[@value="va"]')).click() // select Vatican City as the country
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.name('address.state'))
|
||||
.then(function (stateDropdown) {
|
||||
.then(stateDropdown => {
|
||||
t.equal(stateDropdown.length, 0);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('checkZipCodeRequired', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//input[@name="address.zip"]/following-sibling::' +
|
||||
tap.test('checkZipCodeRequired', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = '//input[@name="address.zip"]/following-sibling::' +
|
||||
'span[@class="help-block validation-message"]/span[contains(text(),' +
|
||||
'"This field is required")]';
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -4,41 +4,41 @@
|
|||
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
|
||||
*/
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
var utils = require('./teacher_registration_utils.js');
|
||||
var constants = utils.constants;
|
||||
const utils = require('./teacher_registration_utils.js');
|
||||
const constants = utils.constants;
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(2);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
driver.get(rootUrl + '/educators/register');
|
||||
tap.beforeEach(() => {
|
||||
driver.get(`${rootUrl}/educators/register`);
|
||||
return utils.fillUsernameSlide(driver, seleniumWebdriver);
|
||||
});
|
||||
|
||||
// if the user selects the other gender option, they must input a gender
|
||||
// selects the other gender option and attempt to advance the slide
|
||||
tap.test('checkOtherGenderInput', function (t) {
|
||||
var otherGenderRadio = driver.findElement(seleniumWebdriver.By.xpath('//input[@value="other"' +
|
||||
tap.test('checkOtherGenderInput', t => {
|
||||
const otherGenderRadio = driver.findElement(seleniumWebdriver.By.xpath('//input[@value="other"' +
|
||||
'and @type="radio"]'));
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]/option[2]')).click();
|
||||
otherGenderRadio.click().then(function () {
|
||||
nextStepButton.click().then(function () {
|
||||
otherGenderRadio.click().then(() => {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(constants.generalErrorMessageXpath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -48,12 +48,12 @@ tap.test('checkOtherGenderInput', function (t) {
|
|||
|
||||
// the user must select a gender
|
||||
// tries to advance the slide without selecting a gender
|
||||
tap.test('checkNoGenderInput', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
tap.test('checkNoGenderInput', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]/option[2]')).click();
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(constants.generalErrorMessageXpath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -4,40 +4,40 @@
|
|||
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
|
||||
*/
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
var utils = require('./teacher_registration_utils.js');
|
||||
var constants = utils.constants;
|
||||
const utils = require('./teacher_registration_utils.js');
|
||||
const constants = utils.constants;
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(2);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
driver.get(rootUrl + '/educators/register');
|
||||
driver.get(`${rootUrl}/educators/register`);
|
||||
return utils.fillUsernameSlide(driver, seleniumWebdriver)
|
||||
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
|
||||
});
|
||||
|
||||
// attempts to advance the slide without inputting either name, checks that both give the correct error
|
||||
tap.test('checkFirstNameRequired', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//input[@name="user.name.first"]/following-sibling::' +
|
||||
tap.test('checkFirstNameRequired', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = '//input[@name="user.name.first"]/following-sibling::' +
|
||||
'span[@class="help-block validation-message"]/span[contains(text(),' +
|
||||
'"This field is required")]';
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -45,14 +45,14 @@ tap.test('checkFirstNameRequired', function (t) {
|
|||
});
|
||||
|
||||
// attempts to advance the slide without inputting either name, checks that both give the correct error
|
||||
tap.test('checkLastNameRequired', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//input[@name="user.name.last"]/following-sibling::' +
|
||||
tap.test('checkLastNameRequired', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = '//input[@name="user.name.last"]/following-sibling::' +
|
||||
'span[@class="help-block validation-message"]/span[contains(text(),' +
|
||||
'"This field is required")]';
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -4,41 +4,41 @@
|
|||
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
|
||||
*/
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
var utils = require('./teacher_registration_utils.js');
|
||||
var constants = utils.constants;
|
||||
const utils = require('./teacher_registration_utils.js');
|
||||
const constants = utils.constants;
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(4);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
driver.get(rootUrl + '/educators/register');
|
||||
driver.get(`${rootUrl}/educators/register`);
|
||||
return utils.fillUsernameSlide(driver, seleniumWebdriver)
|
||||
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
|
||||
});
|
||||
|
||||
tap.test('otherFieldRequiredIfChecked', function (t) {
|
||||
var otherCheckbox = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="checkbox" and @value="8"]'));
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//div[@class="other-input"]' + constants.generalErrorMessageXpath;
|
||||
otherCheckbox.click().then(function () {
|
||||
nextStepButton.click().then(function () {
|
||||
tap.test('otherFieldRequiredIfChecked', t => {
|
||||
const otherCheckbox = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="checkbox" and @value="8"]'));
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = `//div[@class="other-input"]${constants.generalErrorMessageXpath}`;
|
||||
otherCheckbox.click().then(() => {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -46,42 +46,42 @@ tap.test('otherFieldRequiredIfChecked', function (t) {
|
|||
});
|
||||
});
|
||||
|
||||
tap.test('checkOrganizationFieldRequired', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//input[@name="organization.name"]/following-sibling::' +
|
||||
tap.test('checkOrganizationFieldRequired', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = '//input[@name="organization.name"]/following-sibling::' +
|
||||
'span[@class="help-block validation-message"]/span[contains(text(),' +
|
||||
'"This field is required")]';
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('checkRoleFieldRequired', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//input[@name="organization.title"]/following-sibling::' +
|
||||
tap.test('checkRoleFieldRequired', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = '//input[@name="organization.title"]/following-sibling::' +
|
||||
'span[@class="help-block validation-message"]/span[contains(text(),' +
|
||||
'"This field is required")]';
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('checkOrganizationTypeRequired', function (t) {
|
||||
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
var errorMessageXPath = '//div[@class="checkbox"]/following-sibling::' +
|
||||
tap.test('checkOrganizationTypeRequired', t => {
|
||||
const nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
|
||||
const errorMessageXPath = '//div[@class="checkbox"]/following-sibling::' +
|
||||
'span[@class="help-block validation-message" and contains(text(),' +
|
||||
'"This field is required")]';
|
||||
nextStepButton.click().then(function () {
|
||||
nextStepButton.click().then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -4,40 +4,40 @@
|
|||
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
|
||||
*/
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
var utils = require('./teacher_registration_utils.js');
|
||||
const utils = require('./teacher_registration_utils.js');
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(1);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
driver.get(rootUrl + '/educators/register');
|
||||
driver.get(`${rootUrl}/educators/register`);
|
||||
return utils.fillUsernameSlide(driver, seleniumWebdriver)
|
||||
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
|
||||
});
|
||||
|
||||
// inputs an invalid phone number and checks that the correct error message appears
|
||||
tap.test('validatePhoneNumber', function (t) {
|
||||
var phoneInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="tel"]'));
|
||||
var errorMessage = 'Please enter a valid phone number';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message"]/span[contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
phoneInput.sendKeys(1234567890).then(function () {
|
||||
tap.test('validatePhoneNumber', t => {
|
||||
const phoneInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="tel"]'));
|
||||
const errorMessage = 'Please enter a valid phone number';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message"]/span[contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
phoneInput.sendKeys(1234567890).then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
|
||||
.then(function (validationMessages) {
|
||||
.then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -5,37 +5,35 @@
|
|||
*/
|
||||
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(5);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
return driver.get(rootUrl + '/educators/register');
|
||||
});
|
||||
tap.beforeEach(() => driver.get(`${rootUrl}/educators/register`));
|
||||
|
||||
// an error message should appear for a username less than 3 characters long
|
||||
// input a username less than 3 characters and look for the validation message
|
||||
tap.test('checkAtLeastThreeCharacters', function (t) {
|
||||
tap.test('checkAtLeastThreeCharacters', t => {
|
||||
// open scratch in a new instance of the browser
|
||||
driver.get('https://scratch.mit.edu/educators/register');
|
||||
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
var errorMessage = 'Usernames must be at least 3 characters';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
usernameInput.sendKeys('hi').then(function () {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
|
||||
const usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
const errorMessage = 'Usernames must be at least 3 characters';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message" and contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
usernameInput.sendKeys('hi').then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -44,16 +42,16 @@ tap.test('checkAtLeastThreeCharacters', function (t) {
|
|||
|
||||
// usernames have to be unique
|
||||
// input a username that exists and check that an error message appears
|
||||
tap.test('checkUsernameExistsError', function (t) {
|
||||
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
var inputUsername = usernameInput.sendKeys('mres');
|
||||
var passwordClick = passwordInput.click();
|
||||
var errorMessage = 'Sorry, that username already exists';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
Promise.all([inputUsername, passwordClick]).then(function () { // eslint-disable-line no-undef
|
||||
var errorBubble = driver.wait(seleniumWebdriver.until
|
||||
tap.test('checkUsernameExistsError', t => {
|
||||
const usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
const passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
const inputUsername = usernameInput.sendKeys('mres');
|
||||
const passwordClick = passwordInput.click();
|
||||
const errorMessage = 'Sorry, that username already exists';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message" and contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
Promise.all([inputUsername, passwordClick]).then(() => { // eslint-disable-line no-undef
|
||||
const errorBubble = driver.wait(seleniumWebdriver.until
|
||||
.elementLocated(seleniumWebdriver.By.xpath(errorMessageXPath)), 10000);
|
||||
t.notEqual(errorBubble, undefined); // eslint-disable-line no-undefined
|
||||
t.end();
|
||||
|
@ -62,13 +60,13 @@ tap.test('checkUsernameExistsError', function (t) {
|
|||
|
||||
// passwords must be at least 6 characters
|
||||
// find the validation message if the input password is less than 6 characters
|
||||
tap.test('checkPasswordAtLeastSixCharacters', function (t) {
|
||||
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
var errorMessage = 'Passwords must be at least six characters';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
passwordInput.sendKeys('hello').then(function () {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
|
||||
tap.test('checkPasswordAtLeastSixCharacters', t => {
|
||||
const passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
const errorMessage = 'Passwords must be at least six characters';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message" and contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
passwordInput.sendKeys('hello').then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -77,15 +75,15 @@ tap.test('checkPasswordAtLeastSixCharacters', function (t) {
|
|||
|
||||
// password cannot be "password"
|
||||
// find the validation message if the user inputs "password"
|
||||
tap.test('checkPasswordNotPassword', function (t) {
|
||||
tap.test('checkPasswordNotPassword', t => {
|
||||
driver.get('https://scratch.mit.edu/educators/register');
|
||||
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
const passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
// keeping "password" in messed with the xPath, may need to find a better way
|
||||
var errorMessage = 'Your password may not be';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
passwordInput.sendKeys('password').then(function () {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
|
||||
const errorMessage = 'Your password may not be';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message" and contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
passwordInput.sendKeys('password').then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -94,18 +92,18 @@ tap.test('checkPasswordNotPassword', function (t) {
|
|||
|
||||
// the username and password cannot be the same
|
||||
// find the validation message if the username and password match
|
||||
tap.test('checkPasswordNotUsername', function (t) {
|
||||
tap.test('checkPasswordNotUsername', t => {
|
||||
driver.get('https://scratch.mit.edu/educators/register');
|
||||
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
var errorMessage = 'Your password may not be your username';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
var usernamePromise = usernameInput.sendKeys('educator');
|
||||
var passwordPromise = passwordInput.sendKeys('educator');
|
||||
const passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
|
||||
const usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
|
||||
const errorMessage = 'Your password may not be your username';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message" and contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
const usernamePromise = usernameInput.sendKeys('educator');
|
||||
const passwordPromise = passwordInput.sendKeys('educator');
|
||||
// wait for both inputs to have the same text, and check for validation message
|
||||
Promise.all([usernamePromise, passwordPromise]).then(function () { // eslint-disable-line no-undef
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
|
||||
Promise.all([usernamePromise, passwordPromise]).then(() => { // eslint-disable-line no-undef
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(validationMessages => {
|
||||
// there should be only one validation message
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
|
|
|
@ -4,27 +4,27 @@
|
|||
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
|
||||
*/
|
||||
require('chromedriver');
|
||||
var seleniumWebdriver = require('selenium-webdriver');
|
||||
var tap = require('tap');
|
||||
const seleniumWebdriver = require('selenium-webdriver');
|
||||
const tap = require('tap');
|
||||
|
||||
var utils = require('./teacher_registration_utils.js');
|
||||
var constants = utils.constants;
|
||||
const utils = require('./teacher_registration_utils.js');
|
||||
const constants = utils.constants;
|
||||
|
||||
// Set test url through environment variable
|
||||
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
const rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
|
||||
|
||||
// chrome driver
|
||||
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
const driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
|
||||
.build();
|
||||
|
||||
tap.plan(3);
|
||||
|
||||
tap.tearDown(function () {
|
||||
tap.tearDown(() => {
|
||||
driver.quit();
|
||||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
driver.get(rootUrl + '/educators/register');
|
||||
driver.get(`${rootUrl}/educators/register`);
|
||||
return utils.fillUsernameSlide(driver, seleniumWebdriver)
|
||||
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
|
||||
|
@ -33,11 +33,11 @@ tap.beforeEach(function () {
|
|||
.then(utils.fillAddressSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
|
||||
});
|
||||
|
||||
tap.test('checkCharacterCountIsCorrect', function (t) {
|
||||
var textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
|
||||
var charCount = driver.findElement(seleniumWebdriver.By.xpath('//p[@class="char-count"]'));
|
||||
textarea.sendKeys('hello').then(function () {
|
||||
charCount.getText().then(function (charCountText) {
|
||||
tap.test('checkCharacterCountIsCorrect', t => {
|
||||
const textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
|
||||
const charCount = driver.findElement(seleniumWebdriver.By.xpath('//p[@class="char-count"]'));
|
||||
textarea.sendKeys('hello').then(() => {
|
||||
charCount.getText().then(charCountText => {
|
||||
t.equal(charCountText, '5/300');
|
||||
t.end();
|
||||
});
|
||||
|
@ -46,24 +46,24 @@ tap.test('checkCharacterCountIsCorrect', function (t) {
|
|||
|
||||
// Inputs more than 300 characters and checks that the char count gets the class 'overmax'
|
||||
// which turns the text orange
|
||||
tap.test('checkCharacterCountTurnsOrangeWhenTooLong', function (t) {
|
||||
var textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
|
||||
var charCount = driver.findElement(seleniumWebdriver.By.xpath('//p[@class="char-count"]'));
|
||||
textarea.sendKeys(constants.loremIpsumTextLong).then(function () {
|
||||
charCount.getAttribute('class').then(function (charCountClasses) {
|
||||
tap.test('checkCharacterCountTurnsOrangeWhenTooLong', t => {
|
||||
const textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
|
||||
const charCount = driver.findElement(seleniumWebdriver.By.xpath('//p[@class="char-count"]'));
|
||||
textarea.sendKeys(constants.loremIpsumTextLong).then(() => {
|
||||
charCount.getAttribute('class').then(charCountClasses => {
|
||||
t.ok(charCountClasses.includes('overmax'));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('checkCharacterCountErrorAppersWhenTooLong', function (t) {
|
||||
var textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
|
||||
var errorMessage = 'Description must be at most 300 characters';
|
||||
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"' +
|
||||
errorMessage + '")]';
|
||||
textarea.sendKeys(constants.loremIpsumTextLong).then(function () {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
|
||||
tap.test('checkCharacterCountErrorAppersWhenTooLong', t => {
|
||||
const textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
|
||||
const errorMessage = 'Description must be at most 300 characters';
|
||||
const errorMessageXPath = `//span[@class="help-block validation-message" and contains(text(),"${
|
||||
errorMessage}")]`;
|
||||
textarea.sendKeys(constants.loremIpsumTextLong).then(() => {
|
||||
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(validationMessages => {
|
||||
t.equal(validationMessages.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -8,117 +8,116 @@ const {
|
|||
clickXpath,
|
||||
containsClass,
|
||||
findByXpath,
|
||||
navigate,
|
||||
signIn
|
||||
} = new SeleniumHelper();
|
||||
|
||||
// Using 1 and 2 here. Hopefully this is not confusing.
|
||||
let username1 = process.env.SMOKE_USERNAME + '4';
|
||||
let username2 = process.env.SMOKE_USERNAME + '5';
|
||||
let password = process.env.SMOKE_PASSWORD;
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const username1 = `${process.env.SMOKE_USERNAME}4`;
|
||||
const username2 = `${process.env.SMOKE_USERNAME}5`;
|
||||
const password = process.env.SMOKE_PASSWORD;
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
||||
// project for comments (owned by username2)
|
||||
let projectId = process.env.COMMENT_PROJECT_ID || 1300008409;
|
||||
let projectUrl = `${rootUrl}/projects/${projectId}`;
|
||||
const projectId = process.env.COMMENT_PROJECT_ID || 1300008409;
|
||||
const projectUrl = `${rootUrl}/projects/${projectId}`;
|
||||
|
||||
// profile for comments (username2)
|
||||
let profileUrl = `${rootUrl}/users/${username2}`;
|
||||
const profileUrl = `${rootUrl}/users/${username2}`;
|
||||
|
||||
// studio for comments (hosted by username2) comments tab
|
||||
let studioId = process.env.COMMENT_STUDIO_ID || 10005646;
|
||||
let studioUrl = `${rootUrl}/studios/${studioId}/comments`;
|
||||
const studioId = process.env.COMMENT_STUDIO_ID || 10005646;
|
||||
const studioUrl = `${rootUrl}/studios/${studioId}/comments`;
|
||||
|
||||
// setup comments to leave
|
||||
let date = new Date();
|
||||
let dateString = `Y:${date.getFullYear()} - M:${date.getMonth() + 1} - D:${date.getDate()} ` +
|
||||
`: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||
let buildNumber = process.env.CIRCLE_BUILD_NUM || dateString;
|
||||
let projectComment = buildNumber + ' project';
|
||||
let profileComment = buildNumber + ' profile';
|
||||
let studioComment = buildNumber + ' studio';
|
||||
// make sure they are unique and will not be censored (avoid numbers that might look like phone numbers or other PII)
|
||||
const date = new Date();
|
||||
const dateString = date.toISOString();
|
||||
const projectComment = `${dateString} project`;
|
||||
const profileComment = `${dateString} profile`;
|
||||
const studioComment = `${dateString} studio`;
|
||||
|
||||
let projectReply = projectComment + ' reply';
|
||||
let profileReply = profileComment + ' reply';
|
||||
let studioReply = studioComment + ' reply';
|
||||
const projectReply = `${projectComment} reply`;
|
||||
const profileReply = `${profileComment} reply`;
|
||||
const studioReply = `${studioComment} reply`;
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
let driver;
|
||||
|
||||
describe('comment tests', async () => {
|
||||
describe('comment tests', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration project comments');
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
describe('leave comments', async () => {
|
||||
describe('leave comments', () => {
|
||||
beforeAll(async () => {
|
||||
await signIn(username1, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
await clickXpath('//a[contains(@class, "user-info")]');
|
||||
await clickText('Sign out');
|
||||
});
|
||||
|
||||
test('leave comment on project', async () => {
|
||||
await driver.get(projectUrl);
|
||||
await navigate(projectUrl);
|
||||
|
||||
// leave the comment
|
||||
let commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||
await commentBox.sendKeys(projectComment);
|
||||
await findByXpath(`//textarea[contains(text(), "${projectComment}")]`);
|
||||
await clickXpath('//button[@class="button compose-post"]');
|
||||
|
||||
// find the comment
|
||||
let commentXpath = await `//div[@class="comment-bubble"]/span/span[contains(text(),` +
|
||||
` "${projectComment}")]`;
|
||||
let postedComment = await findByXpath(commentXpath);
|
||||
let commentVisible = await postedComment.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const commentXpath = `//div[@class="comment-bubble"]/span/span[contains(text(), "${projectComment}")]`;
|
||||
const postedComment = await findByXpath(commentXpath);
|
||||
const commentVisible = await postedComment.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('leave comment on a profile', async () => {
|
||||
await driver.get(profileUrl);
|
||||
await navigate(profileUrl);
|
||||
|
||||
// leave the comment
|
||||
let commentXpath = await '//form[@id="main-post-form"]/div/textArea';
|
||||
let commentArea = await findByXpath(commentXpath);
|
||||
const commentXpath = '//form[@id="main-post-form"]/div/textArea';
|
||||
const commentArea = await findByXpath(commentXpath);
|
||||
await commentArea.sendKeys(profileComment);
|
||||
await clickXpath('//div[@class="button small"]/a[contains(text(), "Post")]');
|
||||
|
||||
// find the comment
|
||||
let newComment = await findByXpath(`//div[@class="comment "]/div/div[contains(text(),` +
|
||||
const newComment = await findByXpath(`//div[@class="comment "]/div/div[contains(text(),` +
|
||||
` "${profileComment}")]`);
|
||||
let commentVisible = await newComment.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const commentVisible = await newComment.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
|
||||
// return to homepage to sign out with www
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
test('leave comment on studio', async () => {
|
||||
await driver.get(studioUrl);
|
||||
await navigate(studioUrl);
|
||||
|
||||
// leave the comment
|
||||
let commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||
await commentBox.sendKeys(studioComment);
|
||||
await findByXpath(`//textarea[contains(text(), "${studioComment}")]`);
|
||||
await clickXpath('//button[@class="button compose-post"]');
|
||||
|
||||
// find the comment
|
||||
let commentXpath = `//div[@class="comment-bubble"]/span/span[contains(text(), "${studioComment}")]`;
|
||||
let postedComment = await findByXpath(commentXpath);
|
||||
let commentVisible = await postedComment.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const commentXpath = `//div[@class="comment-bubble"]/span/span[contains(text(), "${studioComment}")]`;
|
||||
const postedComment = await findByXpath(commentXpath);
|
||||
const commentVisible = await postedComment.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('second user tests', async () => {
|
||||
describe('second user tests', () => {
|
||||
beforeAll(async () => {
|
||||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
|
@ -126,183 +125,182 @@ describe('comment tests', async () => {
|
|||
|
||||
// get notifications
|
||||
test('get notification badge for comments', async () => {
|
||||
let messages = await findByXpath('//span[@class = "message-count show"]');
|
||||
let messagesVisible = await messages.isDisplayed();
|
||||
await expect(messagesVisible).toBe(true);
|
||||
const messages = await findByXpath('//span[@class = "message-count show"]');
|
||||
const messagesVisible = await messages.isDisplayed();
|
||||
expect(messagesVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('click notifications for comments', async () => {
|
||||
await clickXpath('//li[@class="link right messages"]');
|
||||
let messages = await findByXpath('//ul[@class="messages-social-list"]');
|
||||
let messagesVisible = await messages.isDisplayed();
|
||||
await expect(messagesVisible).toBe(true);
|
||||
const messages = await findByXpath('//ul[@class="messages-social-list"]');
|
||||
const messagesVisible = await messages.isDisplayed();
|
||||
expect(messagesVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('project comment message visible', async () => {
|
||||
await driver.get(rootUrl + '/messages');
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
|
||||
let projectMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
const projectMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]`;
|
||||
let projectMessage = await findByXpath(projectMessageXpath);
|
||||
let projectMessageVisible = await projectMessage.isDisplayed();
|
||||
await expect(projectMessageVisible).toBe(true);
|
||||
const projectMessage = await findByXpath(projectMessageXpath);
|
||||
const projectMessageVisible = await projectMessage.isDisplayed();
|
||||
expect(projectMessageVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('profile comment message visible', async () => {
|
||||
await driver.get(rootUrl + '/messages');
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
|
||||
let profileMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
const profileMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]`;
|
||||
let profileMessage = await findByXpath(profileMessageXpath);
|
||||
let profileMessageVisible = await profileMessage.isDisplayed();
|
||||
await expect(profileMessageVisible).toBe(true);
|
||||
const profileMessage = await findByXpath(profileMessageXpath);
|
||||
const profileMessageVisible = await profileMessage.isDisplayed();
|
||||
expect(profileMessageVisible).toBe(true);
|
||||
});
|
||||
|
||||
// studio comments do not send a notification
|
||||
|
||||
test('project message links you to project page', async () => {
|
||||
let projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||
|
||||
await driver.get(rootUrl + '/messages');
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(projectLinkXpath);
|
||||
|
||||
// find green flag overlay
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await gfOverlay.isDisplayed();
|
||||
});
|
||||
|
||||
test('project comment is on project page', async () => {
|
||||
let projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||
|
||||
await driver.get(rootUrl + '/messages');
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(projectLinkXpath);
|
||||
|
||||
let commentXpath = `//span[contains(text(), "${projectComment}")]`;
|
||||
let singleComment = await findByXpath(commentXpath);
|
||||
let commentVisible = await singleComment.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const commentXpath = `//span[contains(text(), "${projectComment}")]`;
|
||||
const singleComment = await findByXpath(commentXpath);
|
||||
const commentVisible = await singleComment.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('project comment is highlighted', async () => {
|
||||
let projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||
let containerXpath = `//span[contains(text(), "${projectComment}")]/../../../..`;
|
||||
const containerXpath = `//span[contains(text(), "${projectComment}")]/../../../..`;
|
||||
|
||||
await driver.get(rootUrl + '/messages');
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(projectLinkXpath);
|
||||
|
||||
let commentContainer = await findByXpath(containerXpath);
|
||||
let isHighlighted = await containsClass(commentContainer, 'highlighted-comment');
|
||||
await expect(isHighlighted).toBe(true);
|
||||
const commentContainer = await findByXpath(containerXpath);
|
||||
const isHighlighted = await containsClass(commentContainer, 'highlighted-comment');
|
||||
expect(isHighlighted).toBe(true);
|
||||
});
|
||||
|
||||
test('profile message links you to profile page', async () => {
|
||||
let profileLinkXpath = await '//p[@class="emoji-text mod-comment" ' +
|
||||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]/../../../` +
|
||||
`p[@class = "comment-message-info"]/span/a[2]`;
|
||||
await driver.get(rootUrl + '/messages');
|
||||
'p[@class = "comment-message-info"]/span/a[2]';
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(profileLinkXpath);
|
||||
|
||||
// find profile data
|
||||
let profileDataXpath = '//div[@id="profile-data"]';
|
||||
let pathToUsername = '/div[@class="box-head"]/div[@class="header-text"]/h2';
|
||||
const profileDataXpath = '//div[@id="profile-data"]';
|
||||
const pathToUsername = '/div[@class="box-head"]/div[@class="header-text"]/h2';
|
||||
await findByXpath(profileDataXpath);
|
||||
|
||||
let header = await findByXpath(profileDataXpath + pathToUsername);
|
||||
let uname = await header.getText();
|
||||
await expect(uname).toBe(username2);
|
||||
const header = await findByXpath(profileDataXpath + pathToUsername);
|
||||
const uname = await header.getText();
|
||||
expect(uname).toBe(username2);
|
||||
});
|
||||
|
||||
test('profile comment is on profile page', async () => {
|
||||
let profileLinkXpath = await '//p[@class="emoji-text mod-comment" ' +
|
||||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]/../../../` +
|
||||
`p[@class = "comment-message-info"]/span/a[2]`;
|
||||
await driver.get(rootUrl + '/messages');
|
||||
'p[@class = "comment-message-info"]/span/a[2]';
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(profileLinkXpath);
|
||||
|
||||
// find comment
|
||||
let commentXpath = `//div[contains(text(), "${profileComment}")]`;
|
||||
let leftComment = await findByXpath(commentXpath);
|
||||
let commentVisible = await leftComment.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const commentXpath = `//div[contains(text(), "${profileComment}")]`;
|
||||
const leftComment = await findByXpath(commentXpath);
|
||||
const commentVisible = await leftComment.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
test('profile comment is highlighted', async () => {
|
||||
let profileLinkXpath = await '//p[@class="emoji-text mod-comment" ' +
|
||||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]/../../../` +
|
||||
`p[@class = "comment-message-info"]/span/a[2]`;
|
||||
await driver.get(rootUrl + '/messages');
|
||||
'p[@class = "comment-message-info"]/span/a[2]';
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(profileLinkXpath);
|
||||
|
||||
// comment highlighted?
|
||||
let containerXpath = `//div[contains(text(), "${profileComment}")]/../../..`;
|
||||
let commentContainer = await findByXpath(containerXpath);
|
||||
let isHighlighted = await containsClass(commentContainer, 'highlighted');
|
||||
await expect(isHighlighted).toBe(true);
|
||||
const containerXpath = `//div[contains(text(), "${profileComment}")]/../../..`;
|
||||
const commentContainer = await findByXpath(containerXpath);
|
||||
const isHighlighted = await containsClass(commentContainer, 'highlighted');
|
||||
expect(isHighlighted).toBe(true);
|
||||
});
|
||||
|
||||
test('project: reply to comment', async () => {
|
||||
await driver.get(projectUrl);
|
||||
let commentXpath = `//span[contains(text(), "${projectComment}")]/../..`;
|
||||
let replyXpath = commentXpath + '//span[@class = "comment-reply"]';
|
||||
await navigate(projectUrl);
|
||||
const commentXpath = `//span[contains(text(), "${projectComment}")]/../..`;
|
||||
const replyXpath = `${commentXpath}//span[@class = "comment-reply"]`;
|
||||
await clickXpath(replyXpath);
|
||||
|
||||
// type reply
|
||||
let replyRow = '//div[contains(@class, "comment-reply-row")]';
|
||||
let replyComposeXpath = replyRow + '//textArea[@class = "inplace-textarea"]';
|
||||
let composeBox = await findByXpath(replyComposeXpath);
|
||||
const replyRow = '//div[contains(@class, "comment-reply-row")]';
|
||||
const replyComposeXpath = `${replyRow}//textArea[@class = "inplace-textarea"]`;
|
||||
const composeBox = await findByXpath(replyComposeXpath);
|
||||
await composeBox.sendKeys(projectReply);
|
||||
|
||||
// click post
|
||||
let postButton = await findByXpath(replyRow + '//button[@class = "button compose-post"]');
|
||||
await postButton.click();
|
||||
await clickXpath(`${replyRow}//button[@class = "button compose-post"]`);
|
||||
|
||||
let postedReply = await findByXpath(`//span[contains(text(), "${projectReply}")]`);
|
||||
let commentVisible = await postedReply.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const postedReply = await findByXpath(`//span[contains(text(), "${projectReply}")]`);
|
||||
const commentVisible = await postedReply.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('profile reply to comment', async () => {
|
||||
await driver.get(profileUrl);
|
||||
await navigate(profileUrl);
|
||||
// find the comment and click reply
|
||||
let commentXpath = `//div[contains(text(), "${profileComment}")]/..`;
|
||||
await clickXpath(commentXpath + '//a[@class = "reply"]');
|
||||
const commentXpath = `//div[contains(text(), "${profileComment}")]/..`;
|
||||
await clickXpath(`${commentXpath}//a[@class = "reply"]`);
|
||||
|
||||
// select reply box and type reply
|
||||
let replyComposeBox = await findByXpath(commentXpath + '//textArea');
|
||||
const replyComposeBox = await findByXpath(`${commentXpath}//textArea`);
|
||||
await replyComposeBox.sendKeys(profileReply);
|
||||
|
||||
// click post
|
||||
await clickXpath(commentXpath + '//a[contains(text(), "Post")]');
|
||||
await clickXpath(`${commentXpath}//a[contains(text(), "Post")]`);
|
||||
|
||||
// The reply wasn't findable by xpath after several attempts, but it seems
|
||||
// better to have this much of a test
|
||||
});
|
||||
|
||||
test('studio: reply to comment', async () => {
|
||||
await driver.get(studioUrl);
|
||||
await navigate(studioUrl);
|
||||
|
||||
// find the comment and click reply
|
||||
let commentXpath = `//span[contains(text(), "${studioComment}")]/../..`;
|
||||
await clickXpath(commentXpath + '//span[@class = "comment-reply"]');
|
||||
const commentXpath = `//span[contains(text(), "${studioComment}")]/../..`;
|
||||
await clickXpath(`${commentXpath}//span[@class = "comment-reply"]`);
|
||||
|
||||
// type reply
|
||||
let replyRow = '//div[contains(@class, "comment-reply-row")]';
|
||||
let replyComposeXpath = replyRow + '//textArea[@class = "inplace-textarea"]';
|
||||
let composeBox = await findByXpath(replyComposeXpath);
|
||||
const replyRow = '//div[contains(@class, "comment-reply-row")]';
|
||||
const replyComposeXpath = `${replyRow}//textArea[@class = "inplace-textarea"]`;
|
||||
const composeBox = await findByXpath(replyComposeXpath);
|
||||
await composeBox.sendKeys(studioReply);
|
||||
|
||||
// click post
|
||||
let postButton = await findByXpath(replyRow + '//button[@class = "button compose-post"]');
|
||||
const postButton = await findByXpath(`${replyRow}//button[@class = "button compose-post"]`);
|
||||
await postButton.click();
|
||||
|
||||
// find reply
|
||||
let postedReply = await findByXpath(`//span[contains(text(), "${studioReply}")]`);
|
||||
let commentVisible = await postedReply.isDisplayed();
|
||||
await expect(commentVisible).toBe(true);
|
||||
const postedReply = await findByXpath(`//span[contains(text(), "${studioReply}")]`);
|
||||
const commentVisible = await postedReply.isDisplayed();
|
||||
expect(commentVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,10 +5,12 @@ const SeleniumHelper = require('./selenium-helpers.js');
|
|||
const {
|
||||
clickText,
|
||||
buildDriver,
|
||||
findText
|
||||
findText,
|
||||
navigate,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -20,39 +22,43 @@ describe('www-integration footer links', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
await findText('Create stories, games, and animations');
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
// ==== About Scratch column ====
|
||||
|
||||
test('click About Scratch link', async () => {
|
||||
await clickText('About Scratch');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/about\/?$/);
|
||||
});
|
||||
|
||||
test('click For Parents link', async () => {
|
||||
await clickText('For Parents');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/parents\/?$/);
|
||||
});
|
||||
|
||||
test('click For Educators link', async () => {
|
||||
await clickText('For Educators');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/educators\/?$/);
|
||||
});
|
||||
|
||||
test('click For Developers link', async () => {
|
||||
await clickText('For Developers');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/developers\/?$/);
|
||||
});
|
||||
|
||||
|
@ -60,22 +66,25 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Community Guidelines link', async () => {
|
||||
await clickText('Community Guidelines');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/community_guidelines\/?$/);
|
||||
});
|
||||
|
||||
test('click Discussion Forums link', async () => {
|
||||
await clickText('Discussion Forums');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/discuss\/?$/);
|
||||
});
|
||||
|
||||
test('click Statistics link', async () => {
|
||||
await clickText('Statistics');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/statistics\/?$/);
|
||||
});
|
||||
|
||||
|
@ -83,29 +92,33 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Ideas link', async () => {
|
||||
await clickText('Ideas');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/ideas\/?$/);
|
||||
});
|
||||
|
||||
test('click FAQ link', async () => {
|
||||
await clickText('FAQ');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/faq\/?$/);
|
||||
});
|
||||
|
||||
test('click Download link', async () => {
|
||||
await clickText('Download');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/download\/?$/);
|
||||
});
|
||||
|
||||
test('click Contact Us link', async () => {
|
||||
await clickText('Contact Us');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/contact-us\/?$/);
|
||||
});
|
||||
|
||||
|
@ -113,49 +126,43 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Terms of Use link', async () => {
|
||||
await clickText('Terms of Use');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/terms_of_use\/?$/);
|
||||
});
|
||||
|
||||
test('click Privacy Policy link', async () => {
|
||||
await clickText('Privacy Policy');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/privacy_policy\/?$/);
|
||||
});
|
||||
|
||||
test('click Cookies link', async () => {
|
||||
await clickText('Cookies');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/cookies\/?$/);
|
||||
|
||||
// Verify localization of last updated message
|
||||
let lastUpdated = await findText('The Scratch Cookie Policy was last updated June 28, 2023');
|
||||
let lastUpdatedVisible = await lastUpdated.isDisplayed();
|
||||
await expect(lastUpdatedVisible).toBe(true);
|
||||
const lastUpdated = await findText('The Scratch Cookie Policy was last updated');
|
||||
const lastUpdatedVisible = await lastUpdated.isDisplayed();
|
||||
expect(lastUpdatedVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('click DMCA link', async () => {
|
||||
await clickText('DMCA');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/DMCA\/?$/);
|
||||
});
|
||||
|
||||
// ==== SCRATCH FAMILY column ====
|
||||
|
||||
test('click Scratch Conference link', async () => {
|
||||
await clickText('Scratch Conference');
|
||||
let url = await driver.getCurrentUrl();
|
||||
let pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/scratch-conference\/?$/);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// The following links in are skipped because they are not on scratch.mit.edu
|
||||
// The following links in the footer are skipped because they are not part of scratch-www
|
||||
|
||||
// Jobs
|
||||
// Press
|
||||
|
@ -166,3 +173,4 @@ describe('www-integration footer links', () => {
|
|||
// SCRATCH JR (SCRATCHJR)
|
||||
// SCRATCH DAY
|
||||
// SCRATCH FOUNDATION
|
||||
// Scratch Conference
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
const SeleniumHelper = require('./selenium-helpers.js');
|
||||
|
||||
const {
|
||||
buildDriver,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
buildDriver
|
||||
navigate,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -17,40 +19,41 @@ let driver;
|
|||
describe('www-integration project rows', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration project rows');
|
||||
// driver.get(rootUrl);
|
||||
// navigate(rootUrl);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('Featured Projects row title', async () => {
|
||||
let projects = await findByXpath('//div[@class="box"]/div[@class="box-header"]/h4');
|
||||
let projectsText = await projects.getText();
|
||||
await expect(projectsText).toEqual('Featured Projects');
|
||||
const projects = await findByXpath('//div[@class="box"]/div[@class="box-header"]/h4');
|
||||
const projectsText = await projects.getText();
|
||||
expect(projectsText).toEqual('Featured Projects');
|
||||
});
|
||||
|
||||
test('Featured Project link', async () => {
|
||||
await clickXpath('//div[@class="box"][descendant::text()="Featured Projects"]' +
|
||||
'//div[contains(@class, "thumbnail")][1]/a[@class="thumbnail-image"]');
|
||||
let guiPlayer = await findByXpath('//div[@class="guiPlayer"]');
|
||||
let guiPlayerDisplayed = await guiPlayer.isDisplayed();
|
||||
await expect(guiPlayerDisplayed).toBe(true);
|
||||
const guiPlayer = await findByXpath('//div[@class="guiPlayer"]');
|
||||
const guiPlayerDisplayed = await guiPlayer.isDisplayed();
|
||||
expect(guiPlayerDisplayed).toBe(true);
|
||||
});
|
||||
|
||||
test('Featured Studios row title', async () => {
|
||||
let studios = await findByXpath('//div[@class="box"][2]/div[@class="box-header"]/h4');
|
||||
let studiosText = await studios.getText();
|
||||
await expect(studiosText).toEqual('Featured Studios');
|
||||
const studios = await findByXpath('//div[@class="box"][2]/div[@class="box-header"]/h4');
|
||||
const studiosText = await studios.getText();
|
||||
expect(studiosText).toEqual('Featured Studios');
|
||||
});
|
||||
|
||||
test('Featured Studios link', async () => {
|
||||
await clickXpath('//div[@class="box"][descendant::text()="Featured Studios"]' +
|
||||
'//div[contains(@class, "thumbnail")][1]/a[@class="thumbnail-image"]');
|
||||
let studioInfo = await findByXpath('//div[contains(@class, "studio-info")]');
|
||||
let studioInfoDisplayed = await studioInfo.isDisplayed();
|
||||
await expect(studioInfoDisplayed).toBe(true);
|
||||
await waitUntilDocumentReady();
|
||||
const studioInfo = await findByXpath('//div[contains(@class, "studio-info")]');
|
||||
const studioInfoDisplayed = await studioInfo.isDisplayed();
|
||||
expect(studioInfoDisplayed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
const SeleniumHelper = require('./selenium-helpers.js');
|
||||
|
||||
const {
|
||||
findByXpath,
|
||||
buildDriver,
|
||||
clickXpath,
|
||||
buildDriver
|
||||
findByXpath,
|
||||
navigate,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
let takenUsername = process.env.SMOKE_USERNAME;
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const takenUsername = process.env.SMOKE_USERNAME;
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -18,72 +20,77 @@ let driver;
|
|||
describe('www-integration join flow', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration join flow');
|
||||
await driver.get(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await clickXpath('//a[@class="registrationLink"]');
|
||||
await navigate(rootUrl); // navigate to home page
|
||||
await clickXpath('//a[@class="registrationLink"]'); // navigate to join page
|
||||
await waitUntilDocumentReady();
|
||||
});
|
||||
|
||||
test('click Join opens join modal', async () => {
|
||||
let joinModal = await findByXpath('//div[@class = "join-flow-outer-content"]');
|
||||
let modalVisible = await joinModal.isDisplayed();
|
||||
await expect(modalVisible).toBe(true);
|
||||
const joinModal = await findByXpath('//div[@class = "join-flow-outer-content"]');
|
||||
const modalVisible = await joinModal.isDisplayed();
|
||||
expect(modalVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('username validation message appears', async () => {
|
||||
await clickXpath('//input[contains(@name, "username")]');
|
||||
let message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
let messageText = await message.getText();
|
||||
await expect(messageText).toEqual('Don\'t use your real name');
|
||||
|
||||
const clickedInput = await clickXpath('//input[contains(@name, "username")]');
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
|
||||
const message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toEqual('Don\'t use your real name');
|
||||
});
|
||||
|
||||
test('password validation message appears', async () => {
|
||||
await clickXpath('//input[contains(@name, "password")]');
|
||||
let message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
let messageText = await message.getText();
|
||||
await expect(messageText).toContain('Write it down so you remember.');
|
||||
const clickedInput = await clickXpath('//input[contains(@name, "password")]');
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
|
||||
const message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toContain('Write it down so you remember.');
|
||||
});
|
||||
|
||||
test('password validation message appears', async () => {
|
||||
await clickXpath('//input[contains(@name, "passwordConfirm")]');
|
||||
let message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
let messageText = await message.getText();
|
||||
await expect(messageText).toEqual('Type password again');
|
||||
test('password confirmation validation message appears', async () => {
|
||||
const clickedInput = await clickXpath('//input[contains(@name, "passwordConfirm")]');
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
|
||||
const message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toEqual('Type password again');
|
||||
});
|
||||
|
||||
test('username validation: too short', async () => {
|
||||
let textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
await textInput.click();
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||
await textInput.sendKeys('ab');
|
||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||
let message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
let messageText = await message.getText();
|
||||
await expect(messageText).toContain('Must be 3 letters or longer');
|
||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toContain('Must be 3 letters or longer');
|
||||
});
|
||||
|
||||
test('username validation: username taken', async () => {
|
||||
let textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
await textInput.click();
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||
await textInput.sendKeys(takenUsername);
|
||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||
let message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
let messageText = await message.getText();
|
||||
await expect(messageText).toContain('Username taken.');
|
||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toContain('Username taken.');
|
||||
});
|
||||
|
||||
test('username validation: bad word', async () => {
|
||||
let textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
await textInput.click();
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||
// Should be caught by the filter
|
||||
await textInput.sendKeys('xxxxxxxxx');
|
||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||
let message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
let messageText = await message.getText();
|
||||
await expect(messageText).toContain('Username not allowed');
|
||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toContain('Username not allowed');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,15 +7,18 @@ const {
|
|||
clickText,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
signIn
|
||||
navigate,
|
||||
signIn,
|
||||
urlMatches,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let username = process.env.SMOKE_USERNAME + '1';
|
||||
let password = process.env.SMOKE_PASSWORD;
|
||||
const username = `${process.env.SMOKE_USERNAME}1`;
|
||||
const password = process.env.SMOKE_PASSWORD;
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
let myStuffURL = rootUrl + '/mystuff';
|
||||
let rateLimitCheck = process.env.RATE_LIMIT_CHECK || rootUrl;
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const myStuffURL = `${rootUrl}/mystuff`;
|
||||
const rateLimitCheck = process.env.RATE_LIMIT_CHECK || rootUrl;
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -24,109 +27,110 @@ let driver;
|
|||
describe('www-integration my_stuff', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration my_stuff');
|
||||
await driver.get(rootUrl);
|
||||
await driver.sleep(1000);
|
||||
await navigate(rootUrl);
|
||||
await signIn(username, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('verify My Stuff structure (tabs, title)', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
let header = await findByXpath('//div[@class="box-head"]/h2');
|
||||
let headerVisible = await header.isDisplayed();
|
||||
await expect(headerVisible).toBe(true);
|
||||
let allTab = await findByXpath('//li[@data-tab="projects"]/a');
|
||||
let allTabVisible = await allTab.isDisplayed();
|
||||
await expect(allTabVisible).toBe(true);
|
||||
let sharedTab = await findByXpath('//li[@data-tab="shared"]/a');
|
||||
let sharedTabVisible = await sharedTab.isDisplayed();
|
||||
await expect(sharedTabVisible).toBe(true);
|
||||
let unsharedTab = await findByXpath('//li[@data-tab="unshared"]/a');
|
||||
let unsharedTabVisible = await unsharedTab.isDisplayed();
|
||||
await expect(unsharedTabVisible).toBe(true);
|
||||
let studios = await findByXpath('//li[@data-tab="galleries"]/a');
|
||||
let studiosVisible = await studios.isDisplayed();
|
||||
await expect(studiosVisible).toBe(true);
|
||||
let trash = await findByXpath('//li[@data-tab="trash"]/a');
|
||||
let trashVisible = await trash.isDisplayed();
|
||||
await expect(trashVisible).toBe(true);
|
||||
await navigate(myStuffURL);
|
||||
const header = await findByXpath('//div[@class="box-head"]/h2');
|
||||
const headerVisible = await header.isDisplayed();
|
||||
expect(headerVisible).toBe(true);
|
||||
const allTab = await findByXpath('//li[@data-tab="projects"]/a');
|
||||
const allTabVisible = await allTab.isDisplayed();
|
||||
expect(allTabVisible).toBe(true);
|
||||
const sharedTab = await findByXpath('//li[@data-tab="shared"]/a');
|
||||
const sharedTabVisible = await sharedTab.isDisplayed();
|
||||
expect(sharedTabVisible).toBe(true);
|
||||
const unsharedTab = await findByXpath('//li[@data-tab="unshared"]/a');
|
||||
const unsharedTabVisible = await unsharedTab.isDisplayed();
|
||||
expect(unsharedTabVisible).toBe(true);
|
||||
const studios = await findByXpath('//li[@data-tab="galleries"]/a');
|
||||
const studiosVisible = await studios.isDisplayed();
|
||||
expect(studiosVisible).toBe(true);
|
||||
const trash = await findByXpath('//li[@data-tab="trash"]/a');
|
||||
const trashVisible = await trash.isDisplayed();
|
||||
expect(trashVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('clicking a project title should take you to the project page', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//span[@class="media-info-item title"]');
|
||||
await driver.sleep(6000);
|
||||
let gui = await findByXpath('//div[@class="guiPlayer"]');
|
||||
let guiVisible = await gui.isDisplayed();
|
||||
await expect(guiVisible).toBe(true);
|
||||
await waitUntilDocumentReady();
|
||||
const gui = await findByXpath('//div[@class="guiPlayer"]');
|
||||
const guiVisible = await gui.isDisplayed();
|
||||
expect(guiVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('clicking "see inside" should take you to the editor', async () =>{
|
||||
await driver.get(myStuffURL);
|
||||
test('clicking "see inside" should take you to the editor', async () => {
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//a[@data-control="edit"]');
|
||||
let gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
let gfVisible = await gf.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
await waitUntilDocumentReady();
|
||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
const gfVisible = await gf.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('Add To button should bring up a list of studios', async () =>{
|
||||
await driver.get(myStuffURL);
|
||||
test('Add To button should bring up a list of studios', async () => {
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//div[@id="sidebar"]/ul/li[@data-tab="shared"]');
|
||||
await clickXpath('//div[@data-control="add-to"]');
|
||||
let dropDown = await findByXpath('//div[@class="dropdown-menu"]/ul/li');
|
||||
let dropDownVisible = await dropDown.isDisplayed();
|
||||
await expect(dropDownVisible).toBe(true);
|
||||
const dropDown = await findByXpath('//div[@class="dropdown-menu"]/ul/li');
|
||||
const dropDownVisible = await dropDown.isDisplayed();
|
||||
expect(dropDownVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('+ New Project button should open the editor', async () =>{
|
||||
await driver.get(myStuffURL);
|
||||
test('+ New Project button should open the editor', async () => {
|
||||
await navigate(myStuffURL);
|
||||
await clickText('+ New Project');
|
||||
let gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
let gfVisible = await gf.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
await waitUntilDocumentReady();
|
||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
const gfVisible = await gf.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('+ New Studio button should take you to the studio page', async ()=>{
|
||||
await driver.get(rateLimitCheck);
|
||||
await driver.get(myStuffURL);
|
||||
test('+ New Studio button should take you to the studio page', async () => {
|
||||
await navigate(rateLimitCheck);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
let tabs = await findByXpath('//div[@class="studio-tabs"]');
|
||||
let tabsVisible = await tabs.isDisplayed();
|
||||
await expect(tabsVisible).toBe(true);
|
||||
await waitUntilDocumentReady();
|
||||
const tabs = await findByXpath('//div[@class="studio-tabs"]');
|
||||
const tabsVisible = await tabs.isDisplayed();
|
||||
expect(tabsVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('New studio rate limited to five', async () =>{
|
||||
await driver.get(rateLimitCheck);
|
||||
test('New studio rate limited to five', async () => {
|
||||
await navigate(rateLimitCheck);
|
||||
// 1st studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 2nd studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 3rd studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 4th studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 5th studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 6th studio should fail
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
let alertMessage = await findByXpath('//div[contains(@class, "alert-error")]');
|
||||
let errVisible = await alertMessage.isDisplayed();
|
||||
await expect(errVisible).toBe(true);
|
||||
// findByXpath checks for both presence and visibility
|
||||
const alertMessage = await findByXpath('//div[contains(@class, "alert-error")]');
|
||||
expect(alertMessage).toBeTruthy();
|
||||
|
||||
await driver.get(rateLimitCheck);
|
||||
await navigate(rateLimitCheck);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -4,11 +4,12 @@ const SeleniumHelper = require('./selenium-helpers.js');
|
|||
|
||||
const {
|
||||
clickXpath,
|
||||
buildDriver,
|
||||
findByXpath,
|
||||
buildDriver
|
||||
navigate
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -20,81 +21,81 @@ describe('www-integration navbar links', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('Check text of navbar items', async () => {
|
||||
let create = await findByXpath('//li[@class="link create"]');
|
||||
let createText = await create.getText();
|
||||
await expect(createText).toEqual('Create');
|
||||
const create = await findByXpath('//li[@class="link create"]');
|
||||
const createText = await create.getText();
|
||||
expect(createText).toEqual('Create');
|
||||
|
||||
let explore = await findByXpath('//li[@class="link explore"]');
|
||||
let exploreText = await explore.getText();
|
||||
await expect(exploreText).toEqual('Explore');
|
||||
const explore = await findByXpath('//li[@class="link explore"]');
|
||||
const exploreText = await explore.getText();
|
||||
expect(exploreText).toEqual('Explore');
|
||||
|
||||
let ideas = await findByXpath('//li[@class="link ideas"]');
|
||||
let ideasText = await ideas.getText();
|
||||
await expect(ideasText).toEqual('Ideas');
|
||||
const ideas = await findByXpath('//li[@class="link ideas"]');
|
||||
const ideasText = await ideas.getText();
|
||||
expect(ideasText).toEqual('Ideas');
|
||||
|
||||
let about = await findByXpath('//li[@class="link about"]');
|
||||
let aboutText = await about.getText();
|
||||
await expect(aboutText).toEqual('About');
|
||||
const about = await findByXpath('//li[@class="link about"]');
|
||||
const aboutText = await about.getText();
|
||||
expect(aboutText).toEqual('About');
|
||||
|
||||
let join = await findByXpath('//a[@class="registrationLink"]');
|
||||
let joinText = await join.getText();
|
||||
await expect(joinText).toEqual('Join Scratch');
|
||||
const join = await findByXpath('//a[@class="registrationLink"]');
|
||||
const joinText = await join.getText();
|
||||
expect(joinText).toEqual('Join Scratch');
|
||||
|
||||
let signIn = await findByXpath('//li[@class="link right login-item"]/a');
|
||||
let signInText = await signIn.getText();
|
||||
await expect(signInText).toEqual('Sign in');
|
||||
const signIn = await findByXpath('//li[@class="link right login-item"]/a');
|
||||
const signInText = await signIn.getText();
|
||||
expect(signInText).toEqual('Sign in');
|
||||
});
|
||||
|
||||
test('create when signed out', async () => {
|
||||
await clickXpath('//li[@class="link create"]');
|
||||
let gui = await findByXpath('//div[contains(@class, "gui")]');
|
||||
let guiVisible = await gui.isDisplayed();
|
||||
await expect(guiVisible).toBe(true);
|
||||
const gui = await findByXpath('//div[contains(@class, "gui")]');
|
||||
const guiVisible = await gui.isDisplayed();
|
||||
expect(guiVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('Explore link when signed out', async () => {
|
||||
await clickXpath('//li[@class="link explore"]');
|
||||
let banner = await findByXpath('//h1[@class="title-banner-h1"]');
|
||||
let bannerText = await banner.getText();
|
||||
await expect(bannerText).toEqual('Explore');
|
||||
const banner = await findByXpath('//h1[@class="title-banner-h1"]');
|
||||
const bannerText = await banner.getText();
|
||||
expect(bannerText).toEqual('Explore');
|
||||
});
|
||||
|
||||
test('Ideas link when signed out', async () => {
|
||||
await clickXpath('//li[@class="link ideas"]');
|
||||
let banner = await findByXpath('//div[contains(@class, "ideas-banner")]');
|
||||
let bannerVisible = await banner.isDisplayed();
|
||||
await expect(bannerVisible).toBe(true);
|
||||
const banner = await findByXpath('//div[contains(@class, "ideas-banner")]');
|
||||
const bannerVisible = await banner.isDisplayed();
|
||||
expect(bannerVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('About link when signed out', async () => {
|
||||
await clickXpath('//li[@class="link about"]');
|
||||
let aboutPage = await findByXpath('//div[@class="inner about"]');
|
||||
let aboutPageVisible = await aboutPage.isDisplayed();
|
||||
await expect(aboutPageVisible).toBe(true);
|
||||
const aboutPage = await findByXpath('//div[@class="inner about"]');
|
||||
const aboutPageVisible = await aboutPage.isDisplayed();
|
||||
expect(aboutPageVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('Search Bar', async () => {
|
||||
let searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
const searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys('cat');
|
||||
await driver.sleep(500); // without it sends an empty string on submit
|
||||
await searchBar.submit();
|
||||
let banner = await findByXpath('//h1[@class="title-banner-h1"]');
|
||||
let bannerText = await banner.getText();
|
||||
await expect(bannerText).toEqual('Search');
|
||||
const banner = await findByXpath('//h1[@class="title-banner-h1"]');
|
||||
const bannerText = await banner.getText();
|
||||
expect(bannerText).toEqual('Search');
|
||||
});
|
||||
|
||||
test('Scratch Logo', async () => {
|
||||
await clickXpath('//li[@class="link explore"]');
|
||||
await findByXpath('//h1[@class="title-banner-h1"]');
|
||||
await clickXpath('//li[@class="logo"]');
|
||||
let splash = await findByXpath('//div[@class="splash"]');
|
||||
let splashVisible = await splash.isDisplayed();
|
||||
const splash = await findByXpath('//div[@class="splash"]');
|
||||
const splashVisible = await splash.isDisplayed();
|
||||
expect(splashVisible).toBe(true);
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// some tests use projects owned by user #2
|
||||
|
||||
const SeleniumHelper = require('./selenium-helpers.js');
|
||||
const {until} = require('selenium-webdriver');
|
||||
import path from 'path';
|
||||
|
||||
const {
|
||||
|
@ -11,33 +12,35 @@ const {
|
|||
clickXpath,
|
||||
findText,
|
||||
findByXpath,
|
||||
isSignedIn,
|
||||
signIn,
|
||||
navigate,
|
||||
waitUntilVisible
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
||||
// project IDs and URLs
|
||||
let unownedSharedId = process.env.UNOWNED_SHARED_PROJECT_ID || 1300006196;
|
||||
let unownedSharedUrl = rootUrl + '/projects/' + unownedSharedId;
|
||||
const unownedSharedId = process.env.UNOWNED_SHARED_PROJECT_ID || 1300006196;
|
||||
const unownedSharedUrl = `${rootUrl}/projects/${unownedSharedId}`;
|
||||
|
||||
let ownedSharedId = process.env.OWNED_SHARED_PROJECT_ID || 1300009464;
|
||||
let ownedSharedUrl = rootUrl + '/projects/' + ownedSharedId;
|
||||
const ownedSharedId = process.env.OWNED_SHARED_PROJECT_ID || 1300009464;
|
||||
const ownedSharedUrl = `${rootUrl}/projects/${ownedSharedId}`;
|
||||
|
||||
let ownedUnsharedID = process.env.OWNED_UNSHARED_PROJECT_ID || 1300009465;
|
||||
let ownedUnsharedUrl = rootUrl + '/projects/' + ownedUnsharedID;
|
||||
const ownedUnsharedID = process.env.OWNED_UNSHARED_PROJECT_ID || 1300009465;
|
||||
const ownedUnsharedUrl = `${rootUrl}/projects/${ownedUnsharedID}`;
|
||||
|
||||
let unownedUnsharedID = process.env.UNOWNED_UNSHARED_PROJECT_ID || 1300006306;
|
||||
let unownedUnsharedUrl = rootUrl + '/projects/' + unownedUnsharedID;
|
||||
const unownedUnsharedID = process.env.UNOWNED_UNSHARED_PROJECT_ID || 1300006306;
|
||||
const unownedUnsharedUrl = `${rootUrl}/projects/${unownedUnsharedID}`;
|
||||
|
||||
let unownedSharedScratch2ID = process.env.UNOWNED_SHARED_SCRATCH2_PROJECT_ID || 1300009487;
|
||||
let unownedSharedScratch2Url = rootUrl + '/projects/' + unownedSharedScratch2ID;
|
||||
const unownedSharedScratch2ID = process.env.UNOWNED_SHARED_SCRATCH2_PROJECT_ID || 1300009487;
|
||||
const unownedSharedScratch2Url = `${rootUrl}/projects/${unownedSharedScratch2ID}`;
|
||||
|
||||
let ownedUnsharedScratch2ID = process.env.OWNED_UNSHARED_SCRATCH2_PROJECT_ID || 1300009488;
|
||||
let ownedUnsharedScratch2Url = rootUrl + '/projects/' + ownedUnsharedScratch2ID;
|
||||
const ownedUnsharedScratch2ID = process.env.OWNED_UNSHARED_SCRATCH2_PROJECT_ID || 1300009488;
|
||||
const ownedUnsharedScratch2Url = `${rootUrl}/projects/${ownedUnsharedScratch2ID}`;
|
||||
|
||||
let username = process.env.SMOKE_USERNAME + '6';
|
||||
let password = process.env.SMOKE_PASSWORD;
|
||||
const username = `${process.env.SMOKE_USERNAME}6`;
|
||||
const password = process.env.SMOKE_PASSWORD;
|
||||
|
||||
const remote = process.env.SMOKE_REMOTE || false;
|
||||
|
||||
|
@ -52,62 +55,62 @@ describe('www-integration project-page signed out', () => {
|
|||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration project-page signed out');
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(unownedSharedUrl);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(unownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
// LOGGED OUT TESTS
|
||||
|
||||
test('Find fullscreen button', async () => {
|
||||
await clickXpath('//div[starts-with(@class, "stage_green-flag-overlay")]');
|
||||
await clickXpath('//img[contains(@alt, "Enter full screen mode")]');
|
||||
let fullscreenGui = await findByXpath('//div[@class="guiPlayer fullscreen"]');
|
||||
let guiVisible = await fullscreenGui.isDisplayed();
|
||||
await expect(guiVisible).toBe(true);
|
||||
const fullscreenGui = await findByXpath('//div[@class="guiPlayer fullscreen"]');
|
||||
const guiVisible = await fullscreenGui.isDisplayed();
|
||||
expect(guiVisible).toBe(true);
|
||||
});
|
||||
|
||||
test.skip('Open Copy Link modal', async () => {
|
||||
await clickXpath('//button[@class="button action-button copy-link-button"]');
|
||||
let projectLink = await findByXpath('//input[@name="link"]');
|
||||
let linkValue = await projectLink.getAttribute('value');
|
||||
await expect(linkValue).toEqual(unownedSharedUrl);
|
||||
const projectLink = await findByXpath('//input[@name="link"]');
|
||||
const linkValue = await projectLink.getAttribute('value');
|
||||
expect(linkValue).toEqual(unownedSharedUrl);
|
||||
});
|
||||
|
||||
test('Click Username to go to profile page', async ()=> {
|
||||
test('Click Username to go to profile page', async () => {
|
||||
await clickXpath('//div[@class="title"]/a');
|
||||
let userContent = await findByXpath('//div[@class="user-content"]');
|
||||
let contentVisible = await userContent.isDisplayed();
|
||||
await expect(contentVisible).toBe(true);
|
||||
const userContent = await findByXpath('//div[@class="user-content"]');
|
||||
const contentVisible = await userContent.isDisplayed();
|
||||
expect(contentVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('click See Inside to go to the editor', async ()=> {
|
||||
test('click See Inside to go to the editor', async () => {
|
||||
await clickXpath('//button[@class="button button see-inside-button"]');
|
||||
let infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
let areaVisible = await infoArea.isDisplayed();
|
||||
await expect(areaVisible).toBe(true);
|
||||
const infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
const areaVisible = await infoArea.isDisplayed();
|
||||
expect(areaVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('click View All remixes takes you to remix page', async ()=> {
|
||||
test('click View All remixes takes you to remix page', async () => {
|
||||
await clickXpath('//div[@class="list-header-link"]');
|
||||
let originalLink = await findByXpath('//h2/a');
|
||||
let link = await originalLink.getAttribute('href');
|
||||
await expect(link).toEqual(rootUrl + '/projects/' + unownedSharedId + '/');
|
||||
const originalLink = await findByXpath('//h2/a');
|
||||
const link = await originalLink.getAttribute('href');
|
||||
expect(link).toEqual(`${rootUrl}/projects/${unownedSharedId}/`);
|
||||
});
|
||||
|
||||
// Load an unshared project while signed out, get error
|
||||
test('Load an ushared project you do not own (error)', async () => {
|
||||
await driver.get(unownedUnsharedUrl);
|
||||
let unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||
await navigate(unownedUnsharedUrl);
|
||||
const unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||
await waitUntilVisible(unavailableImage, driver);
|
||||
let unavailableVisible = await unavailableImage.isDisplayed();
|
||||
await expect(unavailableVisible).toBe(true);
|
||||
const unavailableVisible = await unavailableImage.isDisplayed();
|
||||
expect(unavailableVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -117,126 +120,123 @@ describe('www-integration project-page signed in', () => {
|
|||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration project-page signed in');
|
||||
await driver.get(rootUrl);
|
||||
await driver.sleep(1000);
|
||||
await signIn(username, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
// The browser may or may not retain cookies between tests, depending on configuration.
|
||||
await navigate(rootUrl);
|
||||
if (!await isSignedIn()) {
|
||||
await signIn(username, password);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
// LOGGED in TESTS
|
||||
|
||||
// Load a shared project you own
|
||||
test('Load a shared project you own', async () => {
|
||||
await driver.get(ownedSharedUrl);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(ownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
let gfVisible = await gfOverlay.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
// Load a shared project you don't own
|
||||
test('Load a shared project you do not own', async () => {
|
||||
await driver.get(unownedSharedUrl);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(unownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
let gfVisible = await gfOverlay.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
// Load an unshared project you own
|
||||
test('Load an unshared project you own', async () => {
|
||||
await driver.get(ownedUnsharedUrl);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(ownedUnsharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
let gfVisible = await gfOverlay.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
// Load an unshared project you don't own, get error
|
||||
test('Load an ushared project you do not own (error)', async () => {
|
||||
await driver.get(unownedUnsharedUrl);
|
||||
let unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||
await navigate(unownedUnsharedUrl);
|
||||
const unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||
await waitUntilVisible(unavailableImage, driver);
|
||||
let unavailableVisible = await unavailableImage.isDisplayed();
|
||||
await expect(unavailableVisible).toBe(true);
|
||||
const unavailableVisible = await unavailableImage.isDisplayed();
|
||||
expect(unavailableVisible).toBe(true);
|
||||
});
|
||||
|
||||
// Load a shared scratch 2 project you don't own
|
||||
test('Load a shared scratch 2 project you do not own', async () => {
|
||||
await driver.get(unownedSharedScratch2Url);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(unownedSharedScratch2Url);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
let gfVisible = await gfOverlay.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
// Load an unshared scratch 2 project you own
|
||||
test('Load an unshared scratch 2 project you own', async () => {
|
||||
await driver.get(ownedUnsharedScratch2Url);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(ownedUnsharedScratch2Url);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
let gfVisible = await gfOverlay.isDisplayed();
|
||||
await expect(gfVisible).toBe(true);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('www-integration project-creation signed in', () => {
|
||||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration project-creation signed in');
|
||||
await driver.get(rootUrl);
|
||||
await driver.sleep(1000);
|
||||
await signIn(username, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
|
||||
// SauceLabs doesn't have access to the sb3 used in 'load project from file' test
|
||||
// https://support.saucelabs.com/hc/en-us/articles/115003685593-Uploading-Files-to-a-Sauce-Labs-Virtual-Machine-during-a-Test
|
||||
if (remote) {
|
||||
await driver.get('https://github.com/LLK/scratch-www/blob/develop/test/fixtures/project1.sb3');
|
||||
await clickText('Download');
|
||||
await navigate('https://github.com/scratchfoundation/scratch-www/blob/develop/test/fixtures/project1.sb3');
|
||||
await clickXpath('//button[@data-testid="download-raw-button"]');
|
||||
await driver.sleep(3000);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
// The browser may or may not retain cookies between tests, depending on configuration.
|
||||
await navigate(rootUrl);
|
||||
if (!await isSignedIn()) {
|
||||
await signIn(username, password);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('make a copy of a project', async () => {
|
||||
await driver.get(ownedUnsharedUrl + '/editor');
|
||||
let gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
await gf.isDisplayed();
|
||||
await navigate(`${ownedUnsharedUrl}/editor`);
|
||||
await clickXpath(FILE_MENU_XPATH);
|
||||
await clickText('Save as a copy');
|
||||
let successAlert = await findText('Project saved as a copy.');
|
||||
let alertVisible = await successAlert.isDisplayed();
|
||||
await expect(alertVisible).toBe(true);
|
||||
const successAlert = await findText('Project saved as a copy.');
|
||||
const alertVisible = await successAlert.isDisplayed();
|
||||
expect(alertVisible).toBe(true);
|
||||
await driver.sleep(1000);
|
||||
let infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
let areaVisible = await infoArea.isDisplayed();
|
||||
await expect(areaVisible).toBe(true);
|
||||
const infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
const areaVisible = await infoArea.isDisplayed();
|
||||
expect(areaVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('remix a project', async () => {
|
||||
await driver.get(unownedSharedUrl);
|
||||
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await navigate(unownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
await clickXpath('//button[@class="button remix-button"]');
|
||||
let successAlert = await findText('Project saved as a remix.');
|
||||
let alertVisible = await successAlert.isDisplayed();
|
||||
await expect(alertVisible).toBe(true);
|
||||
const successAlert = await findText('Project saved as a remix.');
|
||||
const alertVisible = await successAlert.isDisplayed();
|
||||
expect(alertVisible).toBe(true);
|
||||
await driver.sleep(1000);
|
||||
let infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
let areaVisible = await infoArea.isDisplayed();
|
||||
await expect(areaVisible).toBe(true);
|
||||
const infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
const areaVisible = await infoArea.isDisplayed();
|
||||
expect(areaVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('load project from file', async () => {
|
||||
|
@ -245,29 +245,29 @@ describe('www-integration project-creation signed in', () => {
|
|||
'/Users/chef/Downloads/project1.sb3' :
|
||||
path.resolve(__dirname, '../fixtures/project1.sb3');
|
||||
|
||||
// upload file
|
||||
// create a new project so there's unsaved content to trigger an alert
|
||||
await clickXpath('//li[@class="link create"]');
|
||||
let gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
await gf.isDisplayed();
|
||||
|
||||
// upload file
|
||||
await clickXpath(FILE_MENU_XPATH);
|
||||
await clickText('Load from your computer');
|
||||
await driver.sleep(1000);
|
||||
const input = await findByXpath('//input[@accept=".sb,.sb2,.sb3"]');
|
||||
await input.sendKeys(projectPath);
|
||||
|
||||
// accept alert
|
||||
let alert = await driver.switchTo().alert();
|
||||
await driver.wait(until.alertIsPresent());
|
||||
const alert = await driver.switchTo().alert();
|
||||
await alert.accept();
|
||||
|
||||
// check that project is loaded
|
||||
let spriteTile = await findText('project1-sprite');
|
||||
let spriteTileVisible = await spriteTile.isDisplayed();
|
||||
await expect(spriteTileVisible).toBe(true);
|
||||
const spriteTile = await findText('project1-sprite');
|
||||
const spriteTileVisible = await spriteTile.isDisplayed();
|
||||
expect(spriteTileVisible).toBe(true);
|
||||
|
||||
// check that gui is still there after some time has passed
|
||||
await driver.sleep(1000);
|
||||
let infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
let areaVisible = await infoArea.isDisplayed();
|
||||
await expect(areaVisible).toBe(true);
|
||||
const infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
|
||||
const areaVisible = await infoArea.isDisplayed();
|
||||
expect(areaVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,11 @@ const {
|
|||
buildDriver,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
getKey
|
||||
getKey,
|
||||
navigate
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -21,38 +22,38 @@ describe('www-integration search', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('search converts spaces', async () => {
|
||||
let searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys('Test search string' + getKey('ENTER'));
|
||||
const searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys(`Test search string${getKey('ENTER')}`);
|
||||
|
||||
// check url
|
||||
let url = await driver.getCurrentUrl();
|
||||
await expect(url).toMatch(/^.*\?q=Test%20search%20string$/);
|
||||
const url = await driver.getCurrentUrl();
|
||||
expect(url).toMatch(/^.*\?q=Test%20search%20string$/);
|
||||
});
|
||||
|
||||
test('Search escapes symbols', async () => {
|
||||
let searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys('100% pen' + getKey('ENTER'));
|
||||
const searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys(`100% pen${getKey('ENTER')}`);
|
||||
|
||||
// check url
|
||||
let url = await driver.getCurrentUrl();
|
||||
await expect(url).toMatch(/^.*\?q=100%25%20pen$/);
|
||||
const url = await driver.getCurrentUrl();
|
||||
expect(url).toMatch(/^.*\?q=100%25%20pen$/);
|
||||
});
|
||||
|
||||
test('Switching to studios maintains search string', async () => {
|
||||
let searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys('100% pen' + getKey('ENTER'));
|
||||
const searchBar = await findByXpath('//div[contains(@class, "search-wrapper")]/div/input');
|
||||
await searchBar.sendKeys(`100% pen${getKey('ENTER')}`);
|
||||
|
||||
// switch to studios tab
|
||||
clickXpath('//a/li/span[contains(text(),"Studios")]');
|
||||
await clickXpath('//button//*[contains(text(),"Studios")]');
|
||||
|
||||
// check url
|
||||
let url = await driver.getCurrentUrl();
|
||||
await expect(url).toMatch(/^.*\?q=100%25%20pen$/);
|
||||
const url = await driver.getCurrentUrl();
|
||||
expect(url).toMatch(/^.*\?q=100%25%20pen$/);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
jest.setTimeout(30000); // eslint-disable-line no-undef
|
||||
|
||||
const webdriver = require('selenium-webdriver');
|
||||
const {PageLoadStrategy} = require('selenium-webdriver/lib/capabilities');
|
||||
const bindAll = require('lodash.bindall');
|
||||
|
@ -6,14 +8,116 @@ const chromedriverVersion = require('chromedriver').version;
|
|||
|
||||
const headless = process.env.SMOKE_HEADLESS || false;
|
||||
const remote = process.env.SMOKE_REMOTE || false;
|
||||
const ci = process.env.CI || false;
|
||||
const usingCircle = process.env.CIRCLECI || false;
|
||||
const buildID = process.env.CIRCLE_BUILD_NUM || '0000';
|
||||
const ciBuildPrefix = process.env.CI ?
|
||||
`CI #${process.env.GITHUB_RUN_ID}/${process.env.GITHUB_RUN_ATTEMPT}` :
|
||||
''; // no prefix if not in CI
|
||||
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
||||
const {By, Key, until} = webdriver;
|
||||
|
||||
// The main reason for this timeout is so that we can control the timeout message and report details;
|
||||
// if we hit the Jasmine default timeout then we get a terse message that we can't control.
|
||||
// The Jasmine default timeout is 30 seconds so make sure this is lower.
|
||||
const DEFAULT_TIMEOUT_MILLISECONDS = 20 * 1000;
|
||||
|
||||
// This removes confusing `regenerator-runtime` noise from stack traces.
|
||||
// This is V8-specific code. Please don't use it in a browser or any production code.
|
||||
const oldPrepareStackTrace = Error.prepareStackTrace;
|
||||
Error.prepareStackTrace = function (error, stack) {
|
||||
stack = stack.filter(callsite => {
|
||||
const filename = callsite.getFileName();
|
||||
return filename && !filename.includes('regenerator-runtime');
|
||||
});
|
||||
if (oldPrepareStackTrace) {
|
||||
return oldPrepareStackTrace(error, stack);
|
||||
}
|
||||
return [
|
||||
`${error.constructor.name}: ${error.message}`,
|
||||
...stack.map(callsite => ` at ${callsite.toString()}`)
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* An error thrown by SeleniumHelper.
|
||||
* @extends Error
|
||||
*/
|
||||
class SeleniumHelperError extends Error {
|
||||
/**
|
||||
* Instantiate a new SeleniumHelperError.
|
||||
* @param {string} message The error message for this layer.
|
||||
* @param {Array} [kvList] Optional keys & values to add to the error message, for example to capture arguments.
|
||||
* @example
|
||||
* const e = new SeleniumHelperError('something failed', [{xpath}, {somethingElse}])
|
||||
* try {
|
||||
* doThings();
|
||||
* } catch (inner) {
|
||||
* throw await e.chain(inner, driver);
|
||||
* }
|
||||
*/
|
||||
constructor (message, kvList = []) {
|
||||
const baseMessage = [
|
||||
message,
|
||||
...kvList.map(kv => ` ${Object.keys(kv)[0]}: ${Object.values(kv)[0]}`)
|
||||
].join('\n');
|
||||
super(baseMessage);
|
||||
Object.setPrototypeOf(this, SeleniumHelperError.prototype); // see https://stackoverflow.com/a/41102306
|
||||
this.name = 'SeleniumHelperError';
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new layer to the error chain.
|
||||
* Collects context from the webdriver if it is present AND this is the innermost `SeleniumHelperError`.
|
||||
* @param {Error|SeleniumHelperError} innerError The error to add to the chain.
|
||||
* @param {webdriver.ThenableWebDriver} [driver] Optional webdriver instance to collect context from.
|
||||
* @returns {Promise<SeleniumHelperError>} This error, with the new layer added.
|
||||
*/
|
||||
async chain (innerError, driver) {
|
||||
const messageLines = [
|
||||
this.message,
|
||||
innerError.message
|
||||
];
|
||||
// If the inner error has already collected context, don't collect it again.
|
||||
if (driver && !(innerError && innerError.collectContext)) {
|
||||
await this.collectContext(messageLines, driver);
|
||||
}
|
||||
this.message = messageLines.join('\n');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect error context from the webdriver.
|
||||
* @param {Array<string>} messageLines Add context lines to this array.
|
||||
* @param {webdriver.ThenableWebDriver} driver The webdriver instance to collect context from.
|
||||
* @returns {Promise} A promise that resolves when the context is collected.
|
||||
*/
|
||||
async collectContext (messageLines, driver) {
|
||||
// It would be really nice to wait until `message` time to collect all this information,
|
||||
// but that's not an option because of all these async calls.
|
||||
const [
|
||||
url,
|
||||
title,
|
||||
// pageSource,
|
||||
logEntries
|
||||
] = await Promise.all([
|
||||
driver.getCurrentUrl(),
|
||||
driver.getTitle(),
|
||||
// driver.getPageSource(),
|
||||
driver.manage()
|
||||
.logs()
|
||||
.get('browser')
|
||||
]);
|
||||
messageLines.push(
|
||||
`Browser URL: ${url}`,
|
||||
`Browser title: ${title}`,
|
||||
'Browser logs:',
|
||||
'*****',
|
||||
...logEntries.map(entry => entry.message),
|
||||
'*****'
|
||||
// 'Browser page source:', '*****', pageSource, '*****'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SeleniumHelper {
|
||||
constructor () {
|
||||
bindAll(this, [
|
||||
|
@ -31,17 +135,33 @@ class SeleniumHelper {
|
|||
'getDriver',
|
||||
'getLogs',
|
||||
'getSauceDriver',
|
||||
'isSignedIn',
|
||||
'navigate',
|
||||
'signIn',
|
||||
'urlMatches',
|
||||
'waitUntilDocumentReady',
|
||||
'waitUntilGone'
|
||||
]);
|
||||
|
||||
// Tests call this static function as if it were a method on an instance.
|
||||
this.waitUntilVisible = SeleniumHelper.waitUntilVisible;
|
||||
|
||||
// this type declaration suppresses IDE type warnings throughout this file
|
||||
/** @type {webdriver.ThenableWebDriver} */
|
||||
this.driver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new webdriver instance. This will use Sauce Labs if the SMOKE_REMOTE environment variable is 'true', or
|
||||
* `chromedriver` otherwise.
|
||||
* @param {string} name The name to give to Sauce Labs.
|
||||
* @returns {webdriver.ThenableWebDriver} The new webdriver instance.
|
||||
*/
|
||||
buildDriver (name) {
|
||||
if (remote === 'true'){
|
||||
let nameToUse;
|
||||
if (ci === 'true'){
|
||||
let ciName = usingCircle ? 'circleCi ' : 'unknown ';
|
||||
nameToUse = ciName + buildID + ' : ' + name;
|
||||
if (ciBuildPrefix){
|
||||
nameToUse = `${ciBuildPrefix}: ${name}`;
|
||||
} else {
|
||||
nameToUse = name;
|
||||
}
|
||||
|
@ -52,9 +172,14 @@ class SeleniumHelper {
|
|||
return this.driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new webdriver instance using `chromedriver`.
|
||||
* You should probably use `buildDriver` instead.
|
||||
* @returns {webdriver.ThenableWebDriver} The new webdriver instance.
|
||||
*/
|
||||
getDriver () {
|
||||
const chromeCapabilities = webdriver.Capabilities.chrome();
|
||||
let args = [];
|
||||
const args = [];
|
||||
if (headless) {
|
||||
args.push('--headless');
|
||||
args.push('window-size=1024,1680');
|
||||
|
@ -62,13 +187,16 @@ class SeleniumHelper {
|
|||
}
|
||||
chromeCapabilities.set('chromeOptions', {args});
|
||||
chromeCapabilities.setPageLoadStrategy(PageLoadStrategy.EAGER);
|
||||
let driver = new webdriver.Builder()
|
||||
const driver = new webdriver.Builder()
|
||||
.forBrowser('chrome')
|
||||
.withCapabilities(chromeCapabilities)
|
||||
.build();
|
||||
return driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The version of chromedriver being used.
|
||||
*/
|
||||
getChromeVersionNumber () {
|
||||
const versionFinder = /\d+\.\d+/;
|
||||
const versionArray = versionFinder.exec(chromedriverVersion);
|
||||
|
@ -78,16 +206,24 @@ class SeleniumHelper {
|
|||
return versionArray[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new webdriver instance using Sauce Labs.
|
||||
* You should probably use `buildDriver` instead.
|
||||
* @param {string} username The Sauce Labs username.
|
||||
* @param {string} accessKey The Sauce Labs access key.
|
||||
* @param {string} name The name to give to Sauce Labs.
|
||||
* @returns {webdriver.ThenableWebDriver} The new webdriver instance.
|
||||
*/
|
||||
getSauceDriver (username, accessKey, name) {
|
||||
const chromeVersion = this.getChromeVersionNumber();
|
||||
// Driver configs can be generated with the Sauce Platform Configurator
|
||||
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
|
||||
let driverConfig = {
|
||||
const driverConfig = {
|
||||
browserName: 'chrome',
|
||||
platform: 'macOS 10.15',
|
||||
version: chromeVersion
|
||||
};
|
||||
var driver = new webdriver.Builder()
|
||||
const driver = new webdriver.Builder()
|
||||
.withCapabilities({
|
||||
browserName: driverConfig.browserName,
|
||||
platform: driverConfig.platform,
|
||||
|
@ -96,113 +232,363 @@ class SeleniumHelper {
|
|||
accessKey: accessKey,
|
||||
name: name
|
||||
})
|
||||
.usingServer(`http://${username}:${accessKey
|
||||
}@ondemand.saucelabs.com:80/wd/hub`)
|
||||
.usingServer(`http://${username}:${accessKey}@ondemand.saucelabs.com:80/wd/hub`)
|
||||
.build();
|
||||
return driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a key string by name.
|
||||
* @example
|
||||
* getKey('ENTER') // => '\uE007'
|
||||
* @param {string} keyName The name of the key to retrieve.
|
||||
* @returns {string} The key.
|
||||
*/
|
||||
getKey (keyName) {
|
||||
return Key[keyName];
|
||||
}
|
||||
|
||||
findByXpath (xpath, timeoutMessage = `findByXpath timed out for path: ${xpath}`) {
|
||||
return this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage)
|
||||
.then(el => (
|
||||
this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS, `${xpath} is not visible`)
|
||||
.then(() => el)
|
||||
));
|
||||
/**
|
||||
* Wait until the document is ready (i.e. the document.readyState is 'complete')
|
||||
* @returns {Promise} A promise that resolves when the document is ready
|
||||
*/
|
||||
async waitUntilDocumentReady () {
|
||||
const outerError = new SeleniumHelperError('waitUntilDocumentReady failed');
|
||||
try {
|
||||
await this.driver.wait(
|
||||
async () => await this.driver.executeScript('return document.readyState;') === 'complete',
|
||||
DEFAULT_TIMEOUT_MILLISECONDS
|
||||
);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
waitUntilGone (element) {
|
||||
return this.driver.wait(until.stalenessOf(element));
|
||||
/**
|
||||
* Navigate to the given URL and wait until the document is ready.
|
||||
* The Selenium docs say the promise returned by `driver.get()` "will be resolved when the document has finished
|
||||
* loading." In practice, that doesn't mean the page is ready for testing. I suspect it comes down to the
|
||||
* difference between "interactive" and "complete" (or `DOMContentLoaded` and `load`).
|
||||
* @param {string} url The URL to navigate to.
|
||||
* @returns {Promise} A promise that resolves when the document is ready
|
||||
*/
|
||||
async navigate (url) {
|
||||
const outerError = new SeleniumHelperError('navigate failed', [{url}]);
|
||||
try {
|
||||
await this.driver.get(url);
|
||||
await this.waitUntilDocumentReady();
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
clickXpath (xpath) {
|
||||
return this.findByXpath(xpath).then(el => el.click());
|
||||
/**
|
||||
* Find an element by xpath.
|
||||
* @param {string} xpath The xpath to search for.
|
||||
* @returns {Promise<webdriver.WebElement>} A promise that resolves to the element.
|
||||
*/
|
||||
async findByXpath (xpath) {
|
||||
const outerError = new SeleniumHelperError('findByXpath failed', [{xpath}]);
|
||||
try {
|
||||
const el = await this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
await this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
return el;
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
clickText (text) {
|
||||
return this.clickXpath(`//*[contains(text(), '${text}')]`);
|
||||
/**
|
||||
* @param {webdriver.WebElement} element Wait until this element is gone (stale).
|
||||
* @returns {Promise} A promise that resolves when the element is gone.
|
||||
*/
|
||||
async waitUntilGone (element) {
|
||||
const outerError = new SeleniumHelperError('waitUntilGone failed', [{element}]);
|
||||
try {
|
||||
await this.driver.wait(until.stalenessOf(element), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
findText (text) {
|
||||
return this.driver.wait(until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`), 5 * 1000));
|
||||
}
|
||||
|
||||
clickButton (text) {
|
||||
return this.clickXpath(`//button[contains(text(), '${text}')]`);
|
||||
}
|
||||
|
||||
findByCss (css) {
|
||||
return this.driver.wait(until.elementLocated(By.css(css), 1000 * 5));
|
||||
}
|
||||
|
||||
clickCss (css) {
|
||||
return this.findByCss(css).then(el => el.click());
|
||||
}
|
||||
|
||||
dragFromXpathToXpath (startXpath, endXpath) {
|
||||
return this.findByXpath(startXpath).then(startEl => {
|
||||
return this.findByXpath(endXpath).then(endEl => {
|
||||
return this.driver.actions()
|
||||
.dragAndDrop(startEl, endEl)
|
||||
.perform();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// must be used on a www page
|
||||
async signIn (username, password) {
|
||||
await this.clickXpath('//li[@class="link right login-item"]/a');
|
||||
let name = await this.findByXpath('//input[@id="frc-username-1088"]');
|
||||
await name.sendKeys(username);
|
||||
let word = await this.findByXpath('//input[@id="frc-password-1088"]');
|
||||
await word.sendKeys(password + this.getKey('ENTER'));
|
||||
await this.findByXpath('//span[contains(@class, "profile-name")]');
|
||||
}
|
||||
|
||||
urlMatches (regex) {
|
||||
return this.driver.wait(until.urlMatches(regex), 1000 * 5);
|
||||
}
|
||||
|
||||
getLogs (whitelist) {
|
||||
return this.driver.manage()
|
||||
.logs()
|
||||
.get('browser')
|
||||
.then((entries) => {
|
||||
return entries.filter((entry) => {
|
||||
const message = entry.message;
|
||||
for (let i = 0; i < whitelist.length; i++) {
|
||||
if (message.indexOf(whitelist[i]) !== -1) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
|
||||
return false;
|
||||
} else if (entry.level !== 'SEVERE') {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring non-SEVERE entry: ' + message);
|
||||
return false;
|
||||
/**
|
||||
* Wait until an element can be found by the provided xpath, then click on it.
|
||||
* @param {string} xpath The xpath to click.
|
||||
* @returns {Promise} A promise that resolves when the element is clicked.
|
||||
*/
|
||||
async clickXpath (xpath) {
|
||||
const outerError = new SeleniumHelperError('clickXpath failed', [{xpath}]);
|
||||
try {
|
||||
return await this.driver.wait(new webdriver.WebElementCondition(
|
||||
'for element click to succeed',
|
||||
async () => {
|
||||
const element = await this.findByXpath(xpath);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
await element.click();
|
||||
return element;
|
||||
} catch (e) {
|
||||
if (e instanceof webdriver.error.ElementClickInterceptedError) {
|
||||
// something is in front of the element we want to click
|
||||
// probably the loading screen
|
||||
// this is the main reason for using wait()
|
||||
return null;
|
||||
}
|
||||
return true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element can be found by the provided text, then click on it.
|
||||
* @param {string} text The text to click.
|
||||
* @returns {Promise} A promise that resolves when the element is clicked.
|
||||
*/
|
||||
async clickText (text) {
|
||||
const outerError = new SeleniumHelperError('clickText failed', [{text}]);
|
||||
try {
|
||||
await this.clickXpath(`//*[contains(text(), '${text}')]`);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element can be found by the provided text.
|
||||
* @param {string} text The text to find.
|
||||
* @returns {Promise<webdriver.WebElement>} The element containing the text.
|
||||
*/
|
||||
async findText (text) {
|
||||
const outerError = new SeleniumHelperError('findText failed', [{text}]);
|
||||
try {
|
||||
return await this.driver.wait(
|
||||
until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`)),
|
||||
DEFAULT_TIMEOUT_MILLISECONDS
|
||||
);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a button can be found by the provided text, then click on it.
|
||||
* @param {string} text The button text to find and click.
|
||||
* @returns {Promise} A promise that resolves when the button is clicked.
|
||||
*/
|
||||
async clickButton (text) {
|
||||
const outerError = new SeleniumHelperError('clickButton failed', [{text}]);
|
||||
try {
|
||||
await this.clickXpath(`//button[contains(text(), '${text}')]`);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element can be found by the provided CSS selector.
|
||||
* @param {string} css The CSS selector to find.
|
||||
* @returns {Promise<webdriver.WebElement>} The element matching the CSS selector.
|
||||
*/
|
||||
async findByCss (css) {
|
||||
const outerError = new SeleniumHelperError('findByCss failed', [{css}]);
|
||||
try {
|
||||
return await this.driver.wait(until.elementLocated(By.css(css)), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element can be found by the provided CSS selector, then click on it.
|
||||
* @param {string} css The CSS selector to find and click.
|
||||
* @returns {Promise} A promise that resolves when the element is clicked.
|
||||
*/
|
||||
async clickCss (css) {
|
||||
const outerError = new SeleniumHelperError('clickCss failed', [{css}]);
|
||||
try {
|
||||
const el = await this.findByCss(css);
|
||||
await el.click();
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the two elements can be found, then drag from the first to the second.
|
||||
* @param {string} startXpath The xpath to drag from.
|
||||
* @param {string} endXpath The xpath to drag to.
|
||||
* @returns {Promise} A promise that resolves when the drag is complete.
|
||||
*/
|
||||
async dragFromXpathToXpath (startXpath, endXpath) {
|
||||
const outerError = new SeleniumHelperError('dragFromXpathToXpath failed', [{startXpath}, {endXpath}]);
|
||||
try {
|
||||
const startEl = await this.findByXpath(startXpath);
|
||||
const endEl = await this.findByXpath(endXpath);
|
||||
await this.driver.actions()
|
||||
.dragAndDrop(startEl, endEl)
|
||||
.perform();
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The xpath to the login button, which is present only if signed out.
|
||||
*/
|
||||
getPathForLogin () {
|
||||
return '//li[@class="link right login-item"]/a';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The xpath to the profile name, which is present only if signed in.
|
||||
*/
|
||||
getPathForProfileName () {
|
||||
return '//span[contains(@class, "profile-name")]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean>} True if the user is signed in, false otherwise.
|
||||
* @throws {SeleniumHelperError} If the user's sign-in state cannot be determined.
|
||||
*/
|
||||
async isSignedIn () {
|
||||
const outerError = new SeleniumHelperError('isSignedIn failed');
|
||||
try {
|
||||
const state = await this.driver.wait(
|
||||
() => this.driver.executeScript(
|
||||
`
|
||||
if (document.evaluate(arguments[0], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
.singleNodeValue) {
|
||||
return 'signed in';
|
||||
}
|
||||
if (document.evaluate(arguments[1], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
.singleNodeValue) {
|
||||
return 'signed out';
|
||||
}
|
||||
`,
|
||||
this.getPathForProfileName(),
|
||||
this.getPathForLogin()
|
||||
),
|
||||
DEFAULT_TIMEOUT_MILLISECONDS
|
||||
);
|
||||
switch (state) {
|
||||
case 'signed in':
|
||||
return true;
|
||||
case 'signed out':
|
||||
return false;
|
||||
default:
|
||||
throw new Error('Could not determine whether or not user is signed in');
|
||||
}
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in on a `scratch-www` page.
|
||||
* @param {string} username The username to sign in with.
|
||||
* @param {string} password The password to sign in with.
|
||||
* @returns {Promise} A promise that resolves when the user is signed in.
|
||||
*/
|
||||
async signIn (username, password) {
|
||||
const outerError = new SeleniumHelperError('signIn failed', [
|
||||
{username},
|
||||
{password: password ? 'provided' : 'absent'}
|
||||
]);
|
||||
try {
|
||||
await this.clickXpath(this.getPathForLogin());
|
||||
const name = await this.findByXpath('//input[@id="frc-username-1088"]');
|
||||
await name.sendKeys(username);
|
||||
const word = await this.findByXpath('//input[@id="frc-password-1088"]');
|
||||
await word.sendKeys(password + this.getKey('ENTER'));
|
||||
await this.waitUntilDocumentReady();
|
||||
await this.findByXpath(this.getPathForProfileName());
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the URL matches the provided regex.
|
||||
* @param {RegExp} regex The regex to match the url against.
|
||||
* @returns {Promise} A promise that resolves when the url matches the regex.
|
||||
*/
|
||||
async urlMatches (regex) {
|
||||
const outerError = new SeleniumHelperError('urlMatches failed', [{regex}]);
|
||||
try {
|
||||
await this.driver.wait(until.urlMatches(regex), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected browser log entries.
|
||||
* @param {Array<string>} whitelist A list of log strings to allow.
|
||||
* @returns {Promise<Array<webdriver.logging.Entry>>} A promise that resolves to the log entries.
|
||||
*/
|
||||
async getLogs (whitelist) {
|
||||
const outerError = new SeleniumHelperError('getLogs failed', [{whitelist}]);
|
||||
try {
|
||||
const entries = await this.driver.manage()
|
||||
.logs()
|
||||
.get('browser');
|
||||
return entries.filter(entry => {
|
||||
const message = entry.message;
|
||||
for (const element of whitelist) {
|
||||
if (message.indexOf(element) !== -1) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
|
||||
return false;
|
||||
} else if (entry.level !== 'SEVERE') { // WARNING: this doesn't do what it looks like it does!
|
||||
// eslint-disable-next-line no-console
|
||||
// console.warn('Ignoring non-SEVERE entry: ' + message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async containsClass (element, cl) {
|
||||
let classes = await element.getAttribute('class');
|
||||
let classList = classes.split(' ');
|
||||
if (classList.includes(cl)){
|
||||
return true;
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async waitUntilVisible (element, driver) {
|
||||
await driver.wait(until.elementIsVisible(element));
|
||||
/**
|
||||
* Check if an element's class attribute contains a given class.
|
||||
* @param {webdriver.WebElement} element The element to check.
|
||||
* @param {string} cl The class to check for.
|
||||
* @returns {Promise<boolean>} True if the element's class attribute contains the given class, false otherwise.
|
||||
*/
|
||||
async containsClass (element, cl) {
|
||||
const outerError = new SeleniumHelperError('containsClass failed', [{element}, {cl}]);
|
||||
try {
|
||||
const classes = await element.getAttribute('class');
|
||||
const classList = classes.split(' ');
|
||||
return classList.includes(cl);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {webdriver.WebElement} element Wait until this element is visible.
|
||||
* @param {webdriver.ThenableWebDriver} driver The webdriver instance.
|
||||
* @returns {Promise} A promise that resolves when the element is visible.
|
||||
*/
|
||||
static async waitUntilVisible (element, driver) {
|
||||
const outerError = new SeleniumHelperError('waitUntilVisible failed', [{element}]);
|
||||
try {
|
||||
await driver.wait(until.elementIsVisible(element), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SeleniumHelper;
|
||||
|
|
|
@ -9,15 +9,16 @@ const {
|
|||
clickXpath,
|
||||
findByXpath,
|
||||
getKey,
|
||||
navigate,
|
||||
signIn,
|
||||
waitUntilVisible
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let username = process.env.SMOKE_USERNAME;
|
||||
let password = process.env.SMOKE_PASSWORD;
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
let scratchr2url = rootUrl + '/users/' + username;
|
||||
let wwwURL = rootUrl;
|
||||
const username = process.env.SMOKE_USERNAME;
|
||||
const password = process.env.SMOKE_PASSWORD;
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const scratchr2url = `${rootUrl}/users/${username}`;
|
||||
const wwwURL = rootUrl;
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
|
@ -34,45 +35,45 @@ describe('www-integration sign-in-and-out', () => {
|
|||
|
||||
describe('sign in', () => {
|
||||
afterEach(async () => {
|
||||
await driver.get(wwwURL);
|
||||
await navigate(wwwURL);
|
||||
await clickXpath('//div[@class="account-nav"]');
|
||||
await clickText('Sign out');
|
||||
});
|
||||
|
||||
test('sign in on www', async () => {
|
||||
await driver.get(wwwURL);
|
||||
await navigate(wwwURL);
|
||||
await driver.sleep(1000);
|
||||
await clickXpath('//li[@class="link right login-item"]/a');
|
||||
let name = await findByXpath('//input[@id="frc-username-1088"]');
|
||||
const name = await findByXpath('//input[@id="frc-username-1088"]');
|
||||
await name.sendKeys(username);
|
||||
let word = await findByXpath('//input[@id="frc-password-1088"]');
|
||||
const word = await findByXpath('//input[@id="frc-password-1088"]');
|
||||
await word.sendKeys(password);
|
||||
await driver.sleep(500);
|
||||
await clickXpath('//button[contains(@class, "button") and ' +
|
||||
'contains(@class, "submit-button") and contains(@class, "white")]');
|
||||
await driver.sleep(500);
|
||||
let element = await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
let text = await element.getText();
|
||||
await expect(text.toLowerCase()).toEqual(username.toLowerCase());
|
||||
const element = await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
const text = await element.getText();
|
||||
expect(text.toLowerCase()).toEqual(username.toLowerCase());
|
||||
});
|
||||
|
||||
test('sign in on scratchr2', async () => {
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
let name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(username);
|
||||
let word = await findByXpath('//input[@name="password"]');
|
||||
const word = await findByXpath('//input[@name="password"]');
|
||||
await word.sendKeys(password);
|
||||
await clickButton('Sign in');
|
||||
let element = await findByXpath('//span[@class="user-name dropdown-toggle"]');
|
||||
let text = await element.getText();
|
||||
await expect(text.toLowerCase()).toEqual(username.toLowerCase());
|
||||
const element = await findByXpath('//span[@class="user-name dropdown-toggle"]');
|
||||
const text = await element.getText();
|
||||
expect(text.toLowerCase()).toEqual(username.toLowerCase());
|
||||
});
|
||||
});
|
||||
|
||||
describe('sign out', () => {
|
||||
beforeEach(async () => {
|
||||
await driver.get(wwwURL);
|
||||
await navigate(wwwURL);
|
||||
await signIn(username, password);
|
||||
await driver.sleep(500);
|
||||
});
|
||||
|
@ -80,73 +81,73 @@ describe('www-integration sign-in-and-out', () => {
|
|||
test('sign out on www', async () => {
|
||||
await clickXpath('//a[contains(@class, "user-info")]');
|
||||
await clickText('Sign out');
|
||||
let element = await findByXpath('//li[@class="link right login-item"]/a/span');
|
||||
let text = await element.getText();
|
||||
await expect(text.toLowerCase()).toEqual('Sign In'.toLowerCase());
|
||||
const element = await findByXpath('//li[@class="link right login-item"]/a/span');
|
||||
const text = await element.getText();
|
||||
expect(text.toLowerCase()).toEqual('Sign In'.toLowerCase());
|
||||
});
|
||||
|
||||
test('sign out on scratchr2', async () => {
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//span[@class="user-name dropdown-toggle"]');
|
||||
await clickXpath('//li[@id="logout"]');
|
||||
let element = await findByXpath('//li[@class="link right login-item"]/a/span');
|
||||
let text = await element.getText();
|
||||
await expect(text.toLowerCase()).toEqual('Sign In'.toLowerCase());
|
||||
const element = await findByXpath('//li[@class="link right login-item"]/a/span');
|
||||
const text = await element.getText();
|
||||
expect(text.toLowerCase()).toEqual('Sign In'.toLowerCase());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('login failures', async () => {
|
||||
describe('login failures', () => {
|
||||
test('sign in with no password in Scratchr2', async () => {
|
||||
let nonsenseUsername = Math.random().toString(36)
|
||||
const nonsenseUsername = Math.random().toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5);
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
let name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(nonsenseUsername + getKey('ENTER'));
|
||||
|
||||
// find error
|
||||
let error = await findByXpath('//form[@id="login"]//div[@class="error"]');
|
||||
const error = await findByXpath('//form[@id="login"]//div[@class="error"]');
|
||||
await waitUntilVisible(error, driver);
|
||||
let errorText = await error.getText();
|
||||
await expect(errorText).toEqual('This field is required.');
|
||||
const errorText = await error.getText();
|
||||
expect(errorText).toEqual('This field is required.');
|
||||
});
|
||||
|
||||
test('sign in with wrong username', async () => {
|
||||
let nonsenseUsername = Math.random().toString(36)
|
||||
const nonsenseUsername = Math.random().toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5);
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
let name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(nonsenseUsername);
|
||||
let word = await findByXpath('//input[@name="password"]');
|
||||
const word = await findByXpath('//input[@name="password"]');
|
||||
await word.sendKeys(password + getKey('ENTER'));
|
||||
|
||||
// find error
|
||||
let error = await findByXpath('//form[@id="login"]//div[@class="error"]');
|
||||
const error = await findByXpath('//form[@id="login"]//div[@class="error"]');
|
||||
await waitUntilVisible(error, driver);
|
||||
let errorText = await error.getText();
|
||||
await expect(errorText).toEqual('Incorrect username or password.');
|
||||
const errorText = await error.getText();
|
||||
expect(errorText).toEqual('Incorrect username or password.');
|
||||
});
|
||||
|
||||
test('sign in with wrong password', async () => {
|
||||
let nonsensePassword = Math.random().toString(36)
|
||||
const nonsensePassword = Math.random().toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5);
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
let name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(username);
|
||||
let word = await findByXpath('//input[@name="password"]');
|
||||
const word = await findByXpath('//input[@name="password"]');
|
||||
await word.sendKeys(nonsensePassword + getKey('ENTER'));
|
||||
|
||||
// find error
|
||||
let error = await findByXpath('//form[@id="login"]//div[@class="error"]');
|
||||
const error = await findByXpath('//form[@id="login"]//div[@class="error"]');
|
||||
await waitUntilVisible(error, driver);
|
||||
let errorText = await error.getText();
|
||||
await expect(errorText).toEqual('Incorrect username or password.');
|
||||
const errorText = await error.getText();
|
||||
expect(errorText).toEqual('Incorrect username or password.');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -6,56 +6,57 @@ const {
|
|||
buildDriver,
|
||||
clickText,
|
||||
containsClass,
|
||||
findByXpath
|
||||
findByXpath,
|
||||
navigate
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
let statisticsPage = rootUrl + '/statistics';
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const statisticsPage = `${rootUrl}/statistics`;
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
let driver;
|
||||
|
||||
describe('www-integration statistics page', async () => {
|
||||
describe('www-integration statistics page', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration statistics page');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(statisticsPage);
|
||||
await navigate(statisticsPage);
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('check Monthly Activity Trends title', async () => {
|
||||
let chartTitle = await findByXpath('//div[contains(@class, "box0")]//h3');
|
||||
let chartTitleText = await chartTitle.getText();
|
||||
await expect(chartTitleText).toBe('Monthly Activity Trends');
|
||||
const chartTitle = await findByXpath('//div[contains(@class, "box0")]//h3');
|
||||
const chartTitleText = await chartTitle.getText();
|
||||
expect(chartTitleText).toBe('Monthly Activity Trends');
|
||||
});
|
||||
|
||||
test('New Projects label on first chart defaults to selected', async () => {
|
||||
let toggleXpath = `(//div[@id="activity_chart"]/*[name()='svg']/*[name()='g']/*[name()='g']/*` +
|
||||
const toggleXpath = `(//div[@id="activity_chart"]/*[name()='svg']/*[name()='g']/*[name()='g']/*` +
|
||||
`[name()='g'])[4]/*[name()='g']/*[name()='g']/*[name()='g']`;
|
||||
let newProjectsToggle = await findByXpath(toggleXpath);
|
||||
let toggleState = await containsClass(newProjectsToggle, 'nv-disabled');
|
||||
await expect(toggleState).toBe(false);
|
||||
const newProjectsToggle = await findByXpath(toggleXpath);
|
||||
const toggleState = await containsClass(newProjectsToggle, 'nv-disabled');
|
||||
expect(toggleState).toBe(false);
|
||||
|
||||
|
||||
});
|
||||
|
||||
test('New Projects label on first chart can be toggled', async () => {
|
||||
let toggleXpath = `(//div[@id="activity_chart"]/*[name()='svg']/*[name()='g']/*[name()='g']/*` +
|
||||
const toggleXpath = `(//div[@id="activity_chart"]/*[name()='svg']/*[name()='g']/*[name()='g']/*` +
|
||||
`[name()='g'])[4]/*[name()='g']/*[name()='g']/*[name()='g']`;
|
||||
let newProjectsToggle = await findByXpath(toggleXpath);
|
||||
const newProjectsToggle = await findByXpath(toggleXpath);
|
||||
|
||||
// toggle off New Projects
|
||||
await clickText('New Projects');
|
||||
let toggleState = await containsClass(newProjectsToggle, 'nv-disabled');
|
||||
await expect(toggleState).toBe(true);
|
||||
expect(toggleState).toBe(true);
|
||||
|
||||
// toggle New Projects on again
|
||||
await clickText('New Projects');
|
||||
toggleState = await containsClass(newProjectsToggle, 'nv-disabled');
|
||||
await expect(toggleState).toBe(false);
|
||||
expect(toggleState).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,24 +3,26 @@
|
|||
import SeleniumHelper from './selenium-helpers.js';
|
||||
|
||||
const {
|
||||
findByXpath,
|
||||
buildDriver,
|
||||
clickXpath,
|
||||
clickText,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
isSignedIn,
|
||||
navigate,
|
||||
signIn
|
||||
} = new SeleniumHelper();
|
||||
|
||||
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
let studioId = process.env.TEST_STUDIO_ID || 10004360;
|
||||
let studioUrl = rootUrl + '/studios/' + studioId;
|
||||
let myStuffURL = rootUrl + '/mystuff';
|
||||
let rateLimitCheck = process.env.RATE_LIMIT_CHECK || rootUrl;
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
const studioId = process.env.TEST_STUDIO_ID || 10004360;
|
||||
const studioUrl = `${rootUrl}/studios/${studioId}`;
|
||||
const myStuffURL = `${rootUrl}/mystuff`;
|
||||
const rateLimitCheck = process.env.RATE_LIMIT_CHECK || rootUrl;
|
||||
|
||||
// since the usernames end in 2 and 3 we're using username2 and username3
|
||||
// username 1 is used in other tests. Hopefully this is not confusing.
|
||||
let username2 = process.env.SMOKE_USERNAME + '2';
|
||||
let username3 = process.env.SMOKE_USERNAME + '3';
|
||||
let password = process.env.SMOKE_PASSWORD;
|
||||
const username2 = `${process.env.SMOKE_USERNAME}2`;
|
||||
const username3 = `${process.env.SMOKE_USERNAME}3`;
|
||||
const password = process.env.SMOKE_PASSWORD;
|
||||
|
||||
let promoteStudioURL;
|
||||
let curatorTab;
|
||||
|
@ -33,35 +35,34 @@ describe('studio page while signed out', () => {
|
|||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration studio-page signed out');
|
||||
await driver.get(rootUrl);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(studioUrl);
|
||||
let studioNav = await findByXpath('//div[@class="studio-tabs"]');
|
||||
await navigate(studioUrl);
|
||||
const studioNav = await findByXpath('//div[@class="studio-tabs"]');
|
||||
await studioNav.isDisplayed();
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('land on projects tab', async () => {
|
||||
await driver.get(studioUrl);
|
||||
let projectGrid = await findByXpath('//div[@class="studio-projects-grid"]');
|
||||
let projectGridDisplayed = await projectGrid.isDisplayed();
|
||||
await expect(projectGridDisplayed).toBe(true);
|
||||
await navigate(studioUrl);
|
||||
const projectGrid = await findByXpath('//div[@class="studio-projects-grid"]');
|
||||
const projectGridDisplayed = await projectGrid.isDisplayed();
|
||||
expect(projectGridDisplayed).toBe(true);
|
||||
});
|
||||
|
||||
test('studio title', async () => {
|
||||
let studioTitle = await findByXpath('//div[@class="studio-title"]');
|
||||
let titleText = await studioTitle.getText();
|
||||
await expect(titleText).toEqual('studio for automated testing');
|
||||
const studioTitle = await findByXpath('//div[@class="studio-title"]');
|
||||
const titleText = await studioTitle.getText();
|
||||
expect(titleText).toEqual('studio for automated testing');
|
||||
});
|
||||
|
||||
test('studio description', async () => {
|
||||
let xpath = '//div[contains(@class, "studio-description")]';
|
||||
let studioDescription = await findByXpath(xpath);
|
||||
let descriptionText = await studioDescription.getText();
|
||||
await expect(descriptionText).toEqual('a description');
|
||||
const xpath = '//div[contains(@class, "studio-description")]';
|
||||
const studioDescription = await findByXpath(xpath);
|
||||
const descriptionText = await studioDescription.getText();
|
||||
expect(descriptionText).toEqual('a description');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -70,53 +71,53 @@ describe('studio management', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration studio management');
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
|
||||
// create a studio for tests
|
||||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await driver.get(rateLimitCheck);
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(rateLimitCheck);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
promoteStudioURL = await driver.getCurrentUrl();
|
||||
curatorTab = await promoteStudioURL + 'curators';
|
||||
curatorTab = `${promoteStudioURL}curators`;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await clickXpath('//a[contains(@class, "user-info")]');
|
||||
await clickText('Sign out');
|
||||
await driver.get(curatorTab);
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
if (await isSignedIn()) {
|
||||
await clickXpath('//a[contains(@class, "user-info")]');
|
||||
await clickText('Sign out');
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => await driver.quit());
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('invite a curator', async () => {
|
||||
// sign in as user2
|
||||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await navigate(curatorTab);
|
||||
|
||||
// invite user3 to curate
|
||||
let inviteBox = await findByXpath('//div[@class="studio-adder-row"]/input');
|
||||
const inviteBox = await findByXpath('//div[@class="studio-adder-row"]/input');
|
||||
await inviteBox.sendKeys(username3);
|
||||
await clickXpath('//div[@class="studio-adder-row"]/button');
|
||||
let inviteAlert = await findByXpath('//div[@class="alert-msg"]'); // the confirm alert
|
||||
let alertText = await inviteAlert.getText();
|
||||
let successText = await `Curator invite sent to "${username3}"`;
|
||||
await expect(alertText).toMatch(successText);
|
||||
const inviteAlert = await findByXpath('//div[@class="alert-msg"]'); // the confirm alert
|
||||
const alertText = await inviteAlert.getText();
|
||||
const successText = `Curator invite sent to "${username3}"`;
|
||||
expect(alertText).toMatch(successText);
|
||||
});
|
||||
|
||||
test('accept curator invite', async () => {
|
||||
// Sign in user3
|
||||
await signIn(username3, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await navigate(curatorTab);
|
||||
|
||||
// accept the curator invite
|
||||
await clickXpath('//button[@class="studio-invitation-button button"]');
|
||||
let acceptSuccess = await findByXpath('//div[contains(@class,"studio-info-box-success")]');
|
||||
let acceptSuccessVisible = await acceptSuccess.isDisplayed();
|
||||
await expect(acceptSuccessVisible).toBe(true);
|
||||
const acceptSuccess = await findByXpath('//div[contains(@class,"studio-info-box-success")]');
|
||||
const acceptSuccessVisible = await acceptSuccess.isDisplayed();
|
||||
expect(acceptSuccessVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('promote to manager', async () => {
|
||||
|
@ -125,23 +126,22 @@ describe('studio management', () => {
|
|||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
// for some reason the user isn't showing up without waiting and reloading the page
|
||||
await driver.sleep(2000);
|
||||
await driver.get(curatorTab);
|
||||
await navigate(curatorTab);
|
||||
|
||||
// promote user3
|
||||
let user3href = await '/users/' + username3;
|
||||
const user3href = `/users/${username3}`;
|
||||
// click kebab menu on the user tile
|
||||
let kebabMenuXpath = await `//a[@href = "${user3href}"]/` +
|
||||
'following-sibling::div[@class="overflow-menu-container"]';
|
||||
await clickXpath(kebabMenuXpath + '/button[@class="overflow-menu-trigger"]');
|
||||
const kebabMenuXpath = `//a[@href = "${user3href}"]/following-sibling::div[@class="overflow-menu-container"]`;
|
||||
await clickXpath(`${kebabMenuXpath}/button[@class="overflow-menu-trigger"]`);
|
||||
// click promote
|
||||
// await clickXpath('//button[@class="promote-menu-button"]'); //<-- I think this will do it
|
||||
await clickXpath(kebabMenuXpath + '/ul/li/button/span[contains(text(), "Promote")]/..');
|
||||
await clickXpath(`${kebabMenuXpath}/ul/li/button/span[contains(text(), "Promote")]/..`);
|
||||
await findByXpath('//div[@class="promote-content"]');
|
||||
// await clickXpath(//button[contains(@class="promote-button")]) <-- add this selector to the button
|
||||
await clickXpath('//div[@class="promote-button-row"]/button/span[contains(text(),"Promote")]/..');
|
||||
let promoteSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
||||
let promoteSuccessVisible = await promoteSuccess.isDisplayed();
|
||||
await expect(promoteSuccessVisible).toBe(true);
|
||||
const promoteSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
||||
const promoteSuccessVisible = await promoteSuccess.isDisplayed();
|
||||
expect(promoteSuccessVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('transfer studio host', async () => {
|
||||
|
@ -149,14 +149,13 @@ describe('studio management', () => {
|
|||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
// for some reason the user isn't showing up without reloading the page
|
||||
await driver.get(curatorTab);
|
||||
await navigate(curatorTab);
|
||||
|
||||
// open kebab menu
|
||||
let user2href = await '/users/' + username2;
|
||||
const user2href = `/users/${username2}`;
|
||||
// click kebab menu on the user tile
|
||||
let kebabMenuXpath = await `//a[@href = "${user2href}"]/` +
|
||||
'following-sibling::div[@class="overflow-menu-container"]';
|
||||
await clickXpath(kebabMenuXpath + '/button[@class="overflow-menu-trigger"]');
|
||||
const kebabMenuXpath = `//a[@href = "${user2href}"]/following-sibling::div[@class="overflow-menu-container"]`;
|
||||
await clickXpath(`${kebabMenuXpath}/button[@class="overflow-menu-trigger"]`);
|
||||
|
||||
// click transfer in dropdown
|
||||
await clickXpath('//button[@class="studio-member-tile-menu-wide"]');
|
||||
|
@ -175,15 +174,15 @@ describe('studio management', () => {
|
|||
await findByXpath('//div[@class="transfer-outcome"]');
|
||||
|
||||
// enter password
|
||||
let passwordInput = await findByXpath('//input[@class="transfer-password-input"]');
|
||||
const passwordInput = await findByXpath('//input[@class="transfer-password-input"]');
|
||||
await passwordInput.sendKeys(password);
|
||||
await findByXpath(`//input[@value="${password}"]`);
|
||||
|
||||
// click confirm
|
||||
// await clickXpath('//button[contains(@class, "confirm-transfer-button")]')
|
||||
await clickXpath('//span[contains(text(), "Confirm")]/..');
|
||||
let transferSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
||||
let successVisible = await transferSuccess.isDisplayed();
|
||||
await expect(successVisible).toBe(true);
|
||||
// findByXpath checks for both presence and visibility
|
||||
const transferSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
||||
expect(transferSuccess).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
* with languages as keys and the missing IDs as values
|
||||
*/
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var tap = require('tap');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const tap = require('tap');
|
||||
|
||||
/**
|
||||
* To get the files (containing message IDs and localized strings for each page in www)
|
||||
* from the intl directory
|
||||
*/
|
||||
var intlDirPath = path.resolve(__dirname, '../../intl/');
|
||||
var intlFiles = fs.readdirSync(intlDirPath);
|
||||
const intlDirPath = path.resolve(__dirname, '../../intl/');
|
||||
const intlFiles = fs.readdirSync(intlDirPath);
|
||||
|
||||
/*
|
||||
* Tells tap whether the test should pass or fail for a given file.
|
||||
|
@ -30,36 +30,36 @@ const noMissingStrings = (fileName, missingMessageId, pagesMissingIds) => {
|
|||
if (Object.keys(missingMessageId).length === 0) {
|
||||
tap.pass();
|
||||
} else {
|
||||
tap.fail(fileName + ' is missing string IDs');
|
||||
tap.fail(`${fileName} is missing string IDs`);
|
||||
pagesMissingIds[fileName] = [];
|
||||
pagesMissingIds[fileName].push(missingMessageId);
|
||||
}
|
||||
};
|
||||
|
||||
var pagesWithLanguagesMissingIds = {};
|
||||
const pagesWithLanguagesMissingIds = {};
|
||||
|
||||
for (var i in intlFiles) {
|
||||
var file = intlFiles[i];
|
||||
var filePath = path.resolve(__dirname, '../../intl/' + file);
|
||||
var pageMessagesString = fs.readFileSync(filePath, 'utf8');
|
||||
for (const i in intlFiles) {
|
||||
const file = intlFiles[i];
|
||||
const filePath = path.resolve(__dirname, `../../intl/${file}`);
|
||||
const pageMessagesString = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
/**
|
||||
* To make the string of the file of the page.intl.js back into useable objects
|
||||
*/
|
||||
var window = {};
|
||||
var pageMessages = eval(pageMessagesString); // eslint-disable-line no-eval
|
||||
const window = {};
|
||||
const pageMessages = eval(pageMessagesString); // eslint-disable-line no-eval
|
||||
|
||||
/**
|
||||
* The goal is to compare the IDs for each language to the IDs for English,
|
||||
* so we need the list of IDs for the given page in English
|
||||
*/
|
||||
var englishIdList = window._messages.en;
|
||||
const englishIdList = window._messages.en;
|
||||
|
||||
var messageIdNotInLanguage = {};
|
||||
const messageIdNotInLanguage = {};
|
||||
|
||||
for (var languageKey in pageMessages) {
|
||||
var currentLanguageObject = pageMessages[languageKey];
|
||||
for (var messageId in englishIdList) {
|
||||
for (const languageKey in pageMessages) {
|
||||
const currentLanguageObject = pageMessages[languageKey];
|
||||
for (const messageId in englishIdList) {
|
||||
if (!(messageId in currentLanguageObject)) {
|
||||
if (typeof messageIdNotInLanguage[languageKey] === 'undefined') {
|
||||
messageIdNotInLanguage[languageKey] = [];
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
var fs = require('fs');
|
||||
var glob = require('glob');
|
||||
var tap = require('tap');
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const tap = require('tap');
|
||||
|
||||
var TRANSLATIONS_PATTERN = './node_modules/scratch-l10n/www/**/*.json';
|
||||
var files = glob.sync(TRANSLATIONS_PATTERN);
|
||||
const TRANSLATIONS_PATTERN = './node_modules/scratch-l10n/www/**/*.json';
|
||||
const files = glob.sync(TRANSLATIONS_PATTERN);
|
||||
|
||||
const checkJson = (data, name) => {
|
||||
try {
|
||||
JSON.parse(data);
|
||||
} catch (e) {
|
||||
tap.fail(name + ' has invalid Json.\n');
|
||||
tap.fail(`${name} has invalid Json.\n`);
|
||||
}
|
||||
};
|
||||
|
||||
tap.test('check valid json', function (t) {
|
||||
files.forEach(function (f) {
|
||||
tap.test('check valid json', t => {
|
||||
files.forEach(f => {
|
||||
const data = fs.readFileSync(f);
|
||||
checkJson(data, f);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
var defaults = require('lodash.defaults');
|
||||
var fastlyConfig = require('../../bin/lib/fastly-config-methods');
|
||||
var routeJson = require('../../src/routes.json');
|
||||
var tap = require('tap');
|
||||
const defaults = require('lodash.defaults');
|
||||
const fastlyConfig = require('../../bin/lib/fastly-config-methods');
|
||||
const routeJson = require('../../src/routes.json');
|
||||
const tap = require('tap');
|
||||
|
||||
var testRoutes = [
|
||||
const testRoutes = [
|
||||
{
|
||||
name: 'less-traveled',
|
||||
pattern: '^/?$',
|
||||
|
@ -20,10 +20,10 @@ var testRoutes = [
|
|||
}
|
||||
];
|
||||
|
||||
var routes = routeJson.map(function (route) {
|
||||
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
|
||||
});
|
||||
var extraAppRoutes = [
|
||||
const routes = routeJson.map(route =>
|
||||
defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route)
|
||||
);
|
||||
const extraAppRoutes = [
|
||||
// Homepage with querystring.
|
||||
// TODO: Should this be added for every route?
|
||||
'/\\?',
|
||||
|
@ -32,35 +32,35 @@ var extraAppRoutes = [
|
|||
];
|
||||
|
||||
|
||||
tap.test('getStaticPaths', function (t) {
|
||||
var staticPaths = fastlyConfig.getStaticPaths(__dirname, '../../build/*');
|
||||
tap.test('getStaticPaths', t => {
|
||||
const staticPaths = fastlyConfig.getStaticPaths(__dirname, '../../build/*');
|
||||
t.type(staticPaths, 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('getViewPaths', function (t) {
|
||||
var viewPaths = fastlyConfig.getViewPaths(testRoutes);
|
||||
tap.test('getViewPaths', t => {
|
||||
const viewPaths = fastlyConfig.getViewPaths(testRoutes);
|
||||
t.type(viewPaths, 'object');
|
||||
t.equal(viewPaths[0], '/?$');
|
||||
t.equal(viewPaths[1], '/more?$');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('pathsToCondition', function (t) {
|
||||
var condition = fastlyConfig.pathsToCondition(['/?$', '/more?$']);
|
||||
tap.test('pathsToCondition', t => {
|
||||
const condition = fastlyConfig.pathsToCondition(['/?$', '/more?$']);
|
||||
t.type(condition, 'string');
|
||||
t.equal(condition, 'req.url~"^(/?$|/more?$)"');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('getAppRouteCondition', function (t) {
|
||||
var condition = fastlyConfig.getAppRouteCondition('../../build/*', routes, extraAppRoutes, __dirname);
|
||||
tap.test('getAppRouteCondition', t => {
|
||||
const condition = fastlyConfig.getAppRouteCondition('../../build/*', routes, extraAppRoutes, __dirname);
|
||||
t.type(condition, 'string');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('testSetTTL', function (t) {
|
||||
var ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
|
||||
tap.test('testSetTTL', t => {
|
||||
const ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
|
||||
t.equal(ttl, '' +
|
||||
'if (itsactuallyttyl) {\n' +
|
||||
' if (req.url ~ "^(/projects/|/fragment/account-nav.json|/session/)" && ' +
|
||||
|
|
|
@ -33,18 +33,6 @@ describe('Captcha test', () => {
|
|||
expect(global.grecaptcha.execute).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Captcha load calls props captchaOnLoad', () => {
|
||||
const props = {
|
||||
onCaptchaLoad: jest.fn()
|
||||
};
|
||||
const wrapper = enzyme.shallow(<Captcha
|
||||
{...props}
|
||||
/>);
|
||||
wrapper.instance().onCaptchaLoad();
|
||||
expect(global.grecaptcha.render).toHaveBeenCalled();
|
||||
expect(props.onCaptchaLoad).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Captcha renders the div google wants', () => {
|
||||
const props = {
|
||||
onCaptchaLoad: jest.fn()
|
||||
|
|
|
@ -7,7 +7,7 @@ import configureStore from 'redux-mock-store';
|
|||
describe('Compose Comment test', () => {
|
||||
const mockStore = configureStore();
|
||||
let _mockFormat;
|
||||
const defaultProps = () =>({
|
||||
const defaultProps = () => ({
|
||||
user: {
|
||||
thumbnailUrl: 'scratch.mit.edu',
|
||||
username: 'auser'
|
||||
|
@ -51,7 +51,7 @@ describe('Compose Comment test', () => {
|
|||
return wrapper;
|
||||
};
|
||||
|
||||
test('status is EDITING when props do not contain a muteStatus ', () => {
|
||||
test('status is EDITING when props do not contain a muteStatus', () => {
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.state.status).toBe('EDITING');
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('Modal & Comment status do not show ', () => {
|
||||
test('Modal & Comment status do not show', () => {
|
||||
const component = getComposeCommentWrapper({});
|
||||
// Comment compsoe box is there
|
||||
expect(component.find('FlexRow.compose-comment').exists()).toEqual(true);
|
||||
|
@ -95,7 +95,7 @@ describe('Compose Comment test', () => {
|
|||
|
||||
});
|
||||
|
||||
test('Error messages shows when comment rejected ', () => {
|
||||
test('Error messages shows when comment rejected', () => {
|
||||
const component = getComposeCommentWrapper({});
|
||||
const commentInstance = component.instance();
|
||||
commentInstance.setState({
|
||||
|
@ -109,7 +109,7 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('Button.compose-cancel').props().disabled).toBe(false);
|
||||
});
|
||||
|
||||
test('No error message shows when comment rejected because user is already muted ', () => {
|
||||
test('No error message shows when comment rejected because user is already muted', () => {
|
||||
const component = getComposeCommentWrapper({});
|
||||
const commentInstance = component.instance();
|
||||
commentInstance.setState({
|
||||
|
@ -278,7 +278,7 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('Button.compose-cancel').props().disabled).toBe(false);
|
||||
});
|
||||
|
||||
test('Mute Modal shows when muteOpen is true ', () => {
|
||||
test('Mute Modal shows when muteOpen is true', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
const store = mockStore({
|
||||
|
@ -390,18 +390,18 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('MuteModal').exists()).toEqual(true);
|
||||
expect(component.find('MuteModal').props().showFeedback).toBe(false);
|
||||
});
|
||||
test('shouldShowMuteModal is false when muteStatus is undefined ', () => {
|
||||
test('shouldShowMuteModal is false when muteStatus is undefined', () => {
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal()).toBe(false);
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is false when list is undefined ', () => {
|
||||
test('shouldShowMuteModal is false when list is undefined', () => {
|
||||
const muteStatus = {};
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(false);
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is false when list empty ', () => {
|
||||
test('shouldShowMuteModal is false when list empty', () => {
|
||||
const muteStatus = {
|
||||
offenses: []
|
||||
};
|
||||
|
@ -409,7 +409,7 @@ describe('Compose Comment test', () => {
|
|||
expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(false);
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is true when only 1 recent offesnse ', () => {
|
||||
test('shouldShowMuteModal is true when only 1 recent offesnse', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
// Since Date.now mocked to 0 above, we just need a small number to make
|
||||
|
@ -426,13 +426,13 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is false when multiple offenses, even if 1 is recent ', () => {
|
||||
test('shouldShowMuteModal is false when multiple offenses, even if 1 is recent', () => {
|
||||
const offenses = [];
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
// Since Date.now mocked to 0 above, we just need a small number to make
|
||||
// it look like it was created more than 2 minutes ago.
|
||||
let offense = {
|
||||
const offense = {
|
||||
expiresAt: '1000',
|
||||
createdAt: '-119' // just shy of two min ago
|
||||
};
|
||||
|
@ -453,7 +453,7 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = () => 0;
|
||||
// Since Date.now mocked to 0 above, we just need a small number to make
|
||||
// it look like it was created more than 2 minutes ago.
|
||||
let offense = {
|
||||
const offense = {
|
||||
expiresAt: '1000',
|
||||
createdAt: '-119' // just shy of two min ago
|
||||
};
|
||||
|
@ -469,7 +469,7 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is false when the user is already muted, even when only 1 recent offesnse ', () => {
|
||||
test('shouldShowMuteModal is false when the user is already muted, even when only 1 recent offesnse', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
// Since Date.now mocked to 0 above, we just need a small number to make
|
||||
|
@ -510,7 +510,7 @@ describe('Compose Comment test', () => {
|
|||
expect(commentInstance.getMuteModalStartStep()).toBe(0);
|
||||
});
|
||||
|
||||
test('getMuteModalStartStep: A reply that got them muted ', () => {
|
||||
test('getMuteModalStartStep: A reply that got them muted', () => {
|
||||
const commentInstance = getComposeCommentWrapper({isReply: true}).instance();
|
||||
commentInstance.setState({
|
||||
status: 'REJECTED_MUTE'
|
||||
|
@ -518,7 +518,7 @@ describe('Compose Comment test', () => {
|
|||
expect(commentInstance.getMuteModalStartStep()).toBe(0);
|
||||
});
|
||||
|
||||
test('getMuteModalStartStep: A reply click when already muted ', () => {
|
||||
test('getMuteModalStartStep: A reply click when already muted', () => {
|
||||
const commentInstance = getComposeCommentWrapper({isReply: true}).instance();
|
||||
commentInstance.setState({
|
||||
status: 'COMPOSE_DISALLOWED'
|
||||
|
@ -526,7 +526,7 @@ describe('Compose Comment test', () => {
|
|||
expect(commentInstance.getMuteModalStartStep()).toBe(1);
|
||||
});
|
||||
|
||||
test('isMuted: expiration is in the future ', () => {
|
||||
test('isMuted: expiration is in the future', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0; // Set "now" to 0 for easier testing.
|
||||
|
||||
|
@ -536,7 +536,7 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('isMuted: expiration is in the past ', () => {
|
||||
test('isMuted: expiration is in the past', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
|
||||
|
@ -546,7 +546,7 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('isMuted: expiration is not set ', () => {
|
||||
test('isMuted: expiration is not set', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('DonateBannerTest', () => {
|
|||
expect(component.find('FormattedMessage[id="donatebanner.askSupport"]').exists()).toEqual(false);
|
||||
|
||||
});
|
||||
test('testing default message comes back after May 21 ', () => {
|
||||
test('testing default message comes back after May 21', () => {
|
||||
// Date after Scratch week
|
||||
global.Date.now = () => new Date(2022, 4, 22).getTime();
|
||||
const component = mountWithIntl(
|
||||
|
|
|
@ -19,7 +19,7 @@ const requestFailureResponse = {
|
|||
// lets us change where remoteRequestResponse points later, without actually changing
|
||||
// mockedValidateEmailRemotely.
|
||||
let remoteRequestResponse = requestSuccessResponse;
|
||||
let mockedValidateEmailRemotely = jest.fn(() => (
|
||||
const mockedValidateEmailRemotely = jest.fn(() => (
|
||||
/* eslint-disable no-undef */
|
||||
Promise.resolve(remoteRequestResponse)
|
||||
/* eslint-enable no-undef */
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import FormikSelect from '../../../src/components/formik-forms/formik-select.jsx';
|
||||
import {Formik} from 'formik';
|
||||
import {Field} from 'formik';
|
||||
import {Field, Formik} from 'formik';
|
||||
|
||||
describe('FormikSelect', () => {
|
||||
test('No validation message without an error', () => {
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('InfoButton', () => {
|
|||
|
||||
// mouseOver info button
|
||||
component.find('div.info-button').simulate('mouseOver');
|
||||
setTimeout(function () { // necessary because mouseover uses debounce
|
||||
setTimeout(() => { // necessary because mouseover uses debounce
|
||||
// crucial: if we don't call update(), then find() below looks through an OLD
|
||||
// version of the DOM! see https://github.com/airbnb/enzyme/issues/1233#issuecomment-358915200
|
||||
component.update();
|
||||
|
@ -70,7 +70,7 @@ describe('InfoButton', () => {
|
|||
|
||||
// mouseLeave from info button
|
||||
component.find('div.info-button').simulate('mouseLeave');
|
||||
setTimeout(function () { // necessary because mouseover uses debounce
|
||||
setTimeout(() => { // necessary because mouseover uses debounce
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
done();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import {shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import {mountWithIntl, shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import MuteModal from '../../../src/components/modal/mute/modal';
|
||||
import Modal from '../../../src/components/modal/base/modal';
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
|||
import MuteStep from '../../../src/components/modal/mute/mute-step';
|
||||
|
||||
describe('MuteStepTest', () => {
|
||||
test('Mute Step with no images ', () => {
|
||||
test('Mute Step with no images', () => {
|
||||
const component = mountWithIntl(
|
||||
<MuteStep
|
||||
header="header text"
|
||||
|
@ -19,7 +19,7 @@ describe('MuteStepTest', () => {
|
|||
|
||||
});
|
||||
|
||||
test('Mute Step with side image ', () => {
|
||||
test('Mute Step with side image', () => {
|
||||
const component = mountWithIntl(
|
||||
<MuteStep
|
||||
sideImg="/path/to/img.png"
|
||||
|
@ -34,7 +34,7 @@ describe('MuteStepTest', () => {
|
|||
|
||||
});
|
||||
|
||||
test('Mute Step with bottom image ', () => {
|
||||
test('Mute Step with bottom image', () => {
|
||||
const component = mountWithIntl(
|
||||
<MuteStep
|
||||
bottomImg="/path/to/img.png"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import {shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import {mountWithIntl, shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import JoinFlowStep from '../../../src/components/join-flow/join-flow-step';
|
||||
import RegistrationErrorStep from '../../../src/components/join-flow/registration-error-step';
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('Studio comments', () => {
|
|||
});
|
||||
test('calling onOpen sets a class on the #viewEl and records in local storage', () => {
|
||||
const component = mountWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
let child = component.find(AdminPanel);
|
||||
const child = component.find(AdminPanel);
|
||||
expect(viewEl.classList.contains(adminPanelOpenClass)).toBe(false);
|
||||
// `act` is a test-util function for making react state updates sync
|
||||
act(child.prop('onOpen'));
|
||||
|
@ -69,7 +69,7 @@ describe('Studio comments', () => {
|
|||
showAdminPanel
|
||||
/>
|
||||
);
|
||||
let child = component.find('iframe');
|
||||
const child = component.find('iframe');
|
||||
expect(child.getDOMNode().src).toMatch('/scratch2-studios/123/adminpanel');
|
||||
});
|
||||
test('responds to closePanel MessageEvent from the iframe', () => {
|
||||
|
|
|
@ -17,7 +17,7 @@ const requestFailureResponse = {
|
|||
// lets us change where remoteRequestResponse points later, without actually changing
|
||||
// mockedValidateUsernameRemotely.
|
||||
let remoteRequestResponse = requestSuccessResponse;
|
||||
let mockedValidateUsernameRemotely = jest.fn(() => (
|
||||
const mockedValidateUsernameRemotely = jest.fn(() => (
|
||||
/* eslint-disable no-undef */
|
||||
Promise.resolve(remoteRequestResponse)
|
||||
/* eslint-enable no-undef */
|
||||
|
|
|
@ -6,7 +6,7 @@ describe('unit test lib/format-time.js', () => {
|
|||
const mockFormatExpression = {
|
||||
format: jest.fn()
|
||||
};
|
||||
beforeEach(() =>{
|
||||
beforeEach(() => {
|
||||
realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
_mockFormat = Intl.RelativeTimeFormat = jest
|
||||
|
@ -14,68 +14,68 @@ describe('unit test lib/format-time.js', () => {
|
|||
.mockImplementation(() => mockFormatExpression);
|
||||
|
||||
});
|
||||
afterEach(()=>{
|
||||
afterEach(() => {
|
||||
global.Date.now = realDateNow;
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test('test timestamp that is 2 minutes in the future', () => {
|
||||
test('timestamp that is 2 minutes in the future', () => {
|
||||
const twoMin = 2 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 2 minutes');
|
||||
format.formatRelativeTime(twoMin, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(2, 'minute');
|
||||
});
|
||||
|
||||
test('test timestamp that is 15 seconds in the future displays 1', () => {
|
||||
test('timestamp that is 15 seconds in the future displays 1', () => {
|
||||
const fifteenSec = 15 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 1 minute');
|
||||
format.formatRelativeTime(fifteenSec, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(1, 'minute');
|
||||
});
|
||||
|
||||
test('test rounding timestamp that is 4.4 minutes rounds to 4', () => {
|
||||
test('rounding timestamp that is 4.4 minutes rounds to 4', () => {
|
||||
const fourPlusMin = 4.4 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 4 minutes');
|
||||
format.formatRelativeTime(fourPlusMin, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(4, 'minute');
|
||||
});
|
||||
|
||||
test('test timestamp that is 95.25 minutes in the future', () => {
|
||||
test('timestamp that is 95.25 minutes in the future', () => {
|
||||
const ninetyFiveMin = 95.25 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 95 minutes');
|
||||
format.formatRelativeTime(ninetyFiveMin, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(95, 'minute');
|
||||
});
|
||||
|
||||
test('test timestamp that is 119 minutes in the future', () => {
|
||||
test('timestamp that is 119 minutes in the future', () => {
|
||||
const ninetyFiveMin = 119 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 199 minutes');
|
||||
format.formatRelativeTime(ninetyFiveMin, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(119, 'minute');
|
||||
});
|
||||
|
||||
test('test timestamp that is 48 hours in the future', () => {
|
||||
test('timestamp that is 48 hours in the future', () => {
|
||||
const fortyEightHrs = 48 * 60 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 48 hours');
|
||||
format.formatRelativeTime(fortyEightHrs, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(48, 'hour');
|
||||
});
|
||||
|
||||
test('test timestamp that is 2.6 hours rounds to 3', () => {
|
||||
test('timestamp that is 2.6 hours rounds to 3', () => {
|
||||
const twoPlusHours = 2.6 * 60 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 3 hours');
|
||||
format.formatRelativeTime(twoPlusHours, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(3, 'hour');
|
||||
});
|
||||
|
||||
test('test timestamp that is 4.2 hours in the future rounds to 4', () => {
|
||||
test('timestamp that is 4.2 hours in the future rounds to 4', () => {
|
||||
const fourPlusHours = 4.2 * 60 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 4 hours');
|
||||
format.formatRelativeTime(fourPlusHours, 'en');
|
||||
expect(mockFormatExpression.format).toHaveBeenCalledWith(4, 'hour');
|
||||
});
|
||||
|
||||
test('test timestamp that is 2 hours in the future is in hours', () => {
|
||||
test('timestamp that is 2 hours in the future is in hours', () => {
|
||||
const twoHours = 2 * 60 * 60 * 1000;
|
||||
mockFormatExpression.format.mockReturnValue('in 2 hours');
|
||||
format.formatRelativeTime(twoHours, 'en');
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('unit test lib/jar.js', () => {
|
|||
expires: expect.anything() // not specifically matching the date because it is hard to mock
|
||||
}));
|
||||
});
|
||||
test('test with opts', () => {
|
||||
test('with opts', () => {
|
||||
jar.set('a', 'b', {option: 'one'});
|
||||
expect(cookie.serialize).toHaveBeenCalled();
|
||||
expect(cookie.serialize).toHaveBeenCalledWith('a', 'b',
|
||||
|
|
|
@ -7,20 +7,17 @@ describe('unit test lib/route.js', () => {
|
|||
});
|
||||
|
||||
test('getURIClassroomToken parses URI paths like /classes/21/register/r9n5f5xk', () => {
|
||||
let response;
|
||||
response = route.getURIClassroomToken('/classes/21/register/r9n5f5xk');
|
||||
const response = route.getURIClassroomToken('/classes/21/register/r9n5f5xk');
|
||||
expect(response).toEqual('r9n5f5xk');
|
||||
});
|
||||
|
||||
test('getURIClassroomToken parses URI paths like /signup/e2dcfkx95', () => {
|
||||
let response;
|
||||
response = route.getURIClassroomToken('/signup/e2dcfkx95');
|
||||
const response = route.getURIClassroomToken('/signup/e2dcfkx95');
|
||||
expect(response).toEqual('e2dcfkx95');
|
||||
});
|
||||
|
||||
test('getURIClassroomToken works with trailing slash', () => {
|
||||
let response;
|
||||
response = route.getURIClassroomToken('/signup/r9n5f5xk/');
|
||||
const response = route.getURIClassroomToken('/signup/r9n5f5xk/');
|
||||
expect(response).toEqual('r9n5f5xk');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
describe('session library', () => {
|
||||
// respond to session requests with empty session object
|
||||
let sessionNoUser = jest.fn((opts, callback) => {
|
||||
const sessionNoUser = jest.fn((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 200});
|
||||
});
|
||||
// respond to session requests with session object that indicates
|
||||
// successfully logged-in user
|
||||
let sessionYesUser = jest.fn((opts, callback) => {
|
||||
const sessionYesUser = jest.fn((opts, callback) => {
|
||||
callback(null, {user: {username: 'test_username'}}, {statusCode: 200});
|
||||
});
|
||||
// respond to first two requests with empty session object; after that,
|
||||
// respond with user in object
|
||||
let sessionNoThenYes = jest.fn((opts, callback) => {
|
||||
const sessionNoThenYes = jest.fn((opts, callback) => {
|
||||
if (sessionNoThenYes.mock.calls.length <= 2) {
|
||||
callback(null, {}, {statusCode: 200});
|
||||
} else {
|
||||
|
@ -19,24 +19,22 @@ describe('session library', () => {
|
|||
});
|
||||
// respond to session requests with response code 404, indicating no session
|
||||
// found for that user
|
||||
let sessionNotFound = jest.fn((opts, callback) => {
|
||||
const sessionNotFound = jest.fn((opts, callback) => {
|
||||
callback(null, null, {statusCode: 404});
|
||||
});
|
||||
// respond to session requests with response code 503, indicating connection failure
|
||||
let sessionConnectFailure = jest.fn((opts, callback) => {
|
||||
const sessionConnectFailure = jest.fn((opts, callback) => {
|
||||
callback(null, null, {statusCode: 503});
|
||||
});
|
||||
|
||||
// by changing whichMockAPIRequest, we can simulate different api responses
|
||||
let whichMockAPIRequest = null;
|
||||
let mockAPIRequest = (opts, callback) => {
|
||||
const mockAPIRequest = (opts, callback) => {
|
||||
whichMockAPIRequest(opts, callback);
|
||||
};
|
||||
|
||||
// mock lib/api.js, and include our mocked version in lib/session.js
|
||||
jest.mock('../../../src/lib/api', () => {
|
||||
return mockAPIRequest;
|
||||
});
|
||||
jest.mock('../../../src/lib/api', () => mockAPIRequest);
|
||||
const sessionLib = require('../../../src/lib/session'); // eslint-disable-line global-require
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -173,7 +173,7 @@ describe('unit test lib/validate.js', () => {
|
|||
});
|
||||
|
||||
test('responseErrorMsg is null in case where there is no dedicated string for that case', () => {
|
||||
let response = validate.responseErrorMsg('username', 'some error that is not covered');
|
||||
const response = validate.responseErrorMsg('username', 'some error that is not covered');
|
||||
expect(response).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -121,7 +121,7 @@ describe('Infinite List redux module', () => {
|
|||
|
||||
describe('ERROR', () => {
|
||||
let action;
|
||||
let error = new Error();
|
||||
const error = new Error();
|
||||
beforeEach(() => {
|
||||
action = module.actions.error(error);
|
||||
});
|
||||
|
@ -145,7 +145,7 @@ describe('Infinite List redux module', () => {
|
|||
describe('action creators', () => {
|
||||
test('module contains actions creators', () => {
|
||||
// The actual action creators are tested above in the reducer tests
|
||||
for (let key in module.actions) {
|
||||
for (const key in module.actions) {
|
||||
expect(typeof module.actions[key]).toBe('function');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ beforeEach(() => {
|
|||
});
|
||||
|
||||
describe('getTopLevelComments', () => {
|
||||
test('replies are only loaded for comments with a reply_count > 0', async () => {
|
||||
test('replies are only loaded for comments with a reply_count > 0', () => {
|
||||
api.mockImplementationOnce((opts, callback) => {
|
||||
expect(opts.uri).toBe('/users/u/projects/123123/comments');
|
||||
const body = [
|
||||
|
@ -43,8 +43,8 @@ describe('getTopLevelComments', () => {
|
|||
expect(state.comments.replies[1]).toBeUndefined();
|
||||
expect(state.comments.replies[60]).toBeUndefined();
|
||||
});
|
||||
test('admin route is used correctly', async () => {
|
||||
api.mockImplementationOnce((opts) => {
|
||||
test('admin route is used correctly', () => {
|
||||
api.mockImplementationOnce(opts => {
|
||||
// NB: this route doesn't include the owner username
|
||||
expect(opts.uri).toBe('/admin/projects/123123/comments');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
|
@ -54,7 +54,7 @@ describe('getTopLevelComments', () => {
|
|||
});
|
||||
|
||||
describe('getCommentById', () => {
|
||||
test('getting a top level comment will not load replies if there arent any', async () => {
|
||||
test('getting a top level comment will not load replies if there arent any', () => {
|
||||
api.mockImplementationOnce((opts, callback) => {
|
||||
expect(opts.uri).toBe('/users/u/projects/123123/comments/111');
|
||||
const body = {id: 111, parent_id: null, reply_count: 0};
|
||||
|
@ -66,8 +66,8 @@ describe('getCommentById', () => {
|
|||
expect(state.comments.replies[111]).toBeUndefined();
|
||||
});
|
||||
|
||||
test('admin route is used correctly', async () => {
|
||||
api.mockImplementationOnce((opts) => {
|
||||
test('admin route is used correctly', () => {
|
||||
api.mockImplementationOnce(opts => {
|
||||
// NB: this route doesn't include the owner username
|
||||
expect(opts.uri).toBe('/admin/projects/123123/comments/111');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
|
@ -75,7 +75,7 @@ describe('getCommentById', () => {
|
|||
store.dispatch(actions.getCommentById(123123, 111, 'u', true, 'a-token'));
|
||||
});
|
||||
|
||||
test('getting a top level comment will load replies', async () => {
|
||||
test('getting a top level comment will load replies', () => {
|
||||
api.mockImplementationOnce((opts, callback) => {
|
||||
expect(opts.uri).toBe('/users/u/projects/123123/comments/111');
|
||||
const body = {id: 111, parent_id: null, reply_count: 2};
|
||||
|
@ -91,7 +91,7 @@ describe('getCommentById', () => {
|
|||
expect(state.comments.replies[111].length).toBe(1);
|
||||
});
|
||||
|
||||
test('getting a reply comment will load the parent comment and its other replies', async () => {
|
||||
test('getting a reply comment will load the parent comment and its other replies', () => {
|
||||
// Expect 3 requests. First 111, which is a reply comment, maybe linked to from messages
|
||||
// Second is for 111's parent, which is 555.
|
||||
// Third is for 555's replies, which returns 111 and 112
|
||||
|
@ -121,5 +121,4 @@ describe.skip('addNewComment', () => { });
|
|||
describe.skip('deleteComment', () => { });
|
||||
describe.skip('reportComment', () => { });
|
||||
describe.skip('resetComments', () => { });
|
||||
describe.skip('reportComment', () => { });
|
||||
describe.skip('getReplies', () => { });
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('session selectors', () => {
|
|||
});
|
||||
|
||||
test('user data', () => {
|
||||
let state = {session: getInitialState()};
|
||||
const state = {session: getInitialState()};
|
||||
const newSession = sessions.user1.session;
|
||||
state.session = sessionReducer(state.session, setSession(newSession));
|
||||
expect(selectUserId(state)).toBe(1);
|
||||
|
@ -28,7 +28,7 @@ describe('session selectors', () => {
|
|||
|
||||
describe('permissions', () => {
|
||||
test('selectIsAdmin', () => {
|
||||
let state = {session: getInitialState()};
|
||||
const state = {session: getInitialState()};
|
||||
const newSession = sessions.user1Admin.session;
|
||||
state.session = sessionReducer(state.session, setSession(newSession));
|
||||
expect(selectIsAdmin(state)).toBe(true);
|
||||
|
@ -37,7 +37,7 @@ describe('session selectors', () => {
|
|||
});
|
||||
|
||||
test('selectIsSocial', () => {
|
||||
let state = {session: getInitialState()};
|
||||
const state = {session: getInitialState()};
|
||||
const newSession = sessions.user1Social.session;
|
||||
state.session = sessionReducer(state.session, setSession(newSession));
|
||||
expect(selectIsSocial(state)).toBe(true);
|
||||
|
|
|
@ -12,7 +12,7 @@ beforeEach(() => {
|
|||
});
|
||||
|
||||
describe('getTopLevelComments', () => {
|
||||
test('replies are only loaded for comments with a reply_count > 0', async () => {
|
||||
test('replies are only loaded for comments with a reply_count > 0', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
|
@ -45,7 +45,7 @@ describe('getTopLevelComments', () => {
|
|||
expect(state.comments.replies[1]).toBeUndefined();
|
||||
expect(state.comments.replies[60]).toBeUndefined();
|
||||
});
|
||||
test('admin route is used when the session shows the user is an admin', async () => {
|
||||
test('admin route is used when the session shows the user is an admin', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123},
|
||||
|
@ -56,7 +56,7 @@ describe('getTopLevelComments', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
api.mockImplementationOnce((opts) => {
|
||||
api.mockImplementationOnce(opts => {
|
||||
expect(opts.uri).toBe('/admin/studios/123123/comments');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
});
|
||||
|
@ -65,7 +65,7 @@ describe('getTopLevelComments', () => {
|
|||
});
|
||||
|
||||
describe('getCommentById', () => {
|
||||
test('getting a top level comment will not load replies if there arent any', async () => {
|
||||
test('getting a top level comment will not load replies if there arent any', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
|
@ -81,7 +81,7 @@ describe('getCommentById', () => {
|
|||
expect(state.comments.replies[111]).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getting a top level comment will load replies', async () => {
|
||||
test('getting a top level comment will load replies', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
|
@ -101,7 +101,7 @@ describe('getCommentById', () => {
|
|||
expect(state.comments.replies[111].length).toBe(1);
|
||||
});
|
||||
|
||||
test('getting a reply comment will load the parent comment and its other replies', async () => {
|
||||
test('getting a reply comment will load the parent comment and its other replies', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
|
@ -135,5 +135,4 @@ describe.skip('addNewComment', () => { });
|
|||
describe.skip('deleteComment', () => { });
|
||||
describe.skip('reportComment', () => { });
|
||||
describe.skip('resetComments', () => { });
|
||||
describe.skip('reportComment', () => { });
|
||||
describe.skip('getReplies', () => { });
|
||||
|
|
|
@ -64,7 +64,7 @@ describe('loadManagers', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
api.mockImplementation((opts) => {
|
||||
api.mockImplementation(opts => {
|
||||
expect(opts.uri).toBe('/admin/studios/123123/managers/');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
});
|
||||
|
@ -120,7 +120,7 @@ describe('loadCurators', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
api.mockImplementation((opts) => {
|
||||
api.mockImplementation(opts => {
|
||||
expect(opts.uri).toBe('/admin/studios/123123/curators/');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ import {sessions, studios} from '../../helpers/state-fixtures.json';
|
|||
|
||||
let state;
|
||||
|
||||
const setStateByRole = (role) => {
|
||||
const setStateByRole = role => {
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
state.session = sessions.user1Admin;
|
||||
|
@ -74,7 +74,7 @@ const setStateByRole = (role) => {
|
|||
state.session = sessions.user1Muted;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown user role in test: ' + role);
|
||||
throw new Error(`Unknown user role in test: ${role}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -588,7 +588,7 @@ describe('studio mute errors', () => {
|
|||
state.session = thisSession;
|
||||
expect(selectShowCommentsGloballyOffError(state)).toBe(true);
|
||||
});
|
||||
test('Do not show comments off error because feature flag is on ', () => {
|
||||
test('Do not show comments off error because feature flag is on', () => {
|
||||
const thisSession = {
|
||||
status: Status.FETCHED,
|
||||
session: {
|
||||
|
@ -600,7 +600,7 @@ describe('studio mute errors', () => {
|
|||
state.session = thisSession;
|
||||
expect(selectShowCommentsGloballyOffError(state)).toBe(false);
|
||||
});
|
||||
test('Do not show comments off error because session not fetched ', () => {
|
||||
test('Do not show comments off error because session not fetched', () => {
|
||||
const thisSession = {
|
||||
status: Status.NOT_FETCHED,
|
||||
session: {
|
||||
|
|
|
@ -52,7 +52,7 @@ describe('loadProjects', () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
api.mockImplementation((opts) => {
|
||||
api.mockImplementation(opts => {
|
||||
expect(opts.uri).toBe('/admin/studios/123123/projects/');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue