diff --git a/.eslintrc b/.eslintrc index df5a544f8..271edc748 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,17 +1,15 @@ { - "root": true, "extends": [ - "react-app", "airbnb", + "plugin:import/typescript", // this is needed because airbnb uses eslint-plugin-import "prettier", - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + "eslint:recommended" ], "plugins": [ - "import", - "jsx-a11y", - "@typescript-eslint" + "@typescript-eslint", + "react-hooks" ], "env": { "browser": true, @@ -21,22 +19,33 @@ }, "globals": { "cy": true, - "Cypress": true + "Cypress": true, + "JSX": "readonly" }, "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, "rules": { + "import/order": "off", + "react/function-component-definition": [ + 2, + { + "namedComponents": "arrow-function" + } + ], + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off", // remove when possible "@typescript-eslint/no-explicit-any": "off", // disable the rule for all files "no-restricted-syntax": "off", // disable the rule for all files "@typescript-eslint/explicit-module-boundary-types": "off", - "react/function-component-definition": [ - "error", - { - "namedComponents": "arrow-function" - } - ], "jsx-a11y/anchor-is-valid": [ "error", { @@ -83,7 +92,6 @@ "error", "never" ], - "react/react-in-jsx-scope": "off", "no-console": [ 1, { @@ -108,7 +116,22 @@ "no-shadow": "off", "@typescript-eslint/no-shadow": [ "error" - ] + ], + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": true + } + ], }, "overrides": [ { diff --git a/.github/workflows/cdelivery-s3-caller.yml b/.github/workflows/cdelivery-s3-caller.yml deleted file mode 100644 index 295040685..000000000 --- a/.github/workflows/cdelivery-s3-caller.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Deploy to staging environment - -# Controls when the action will run. -on: - # Triggers the workflow on repository-dispatch event - repository_dispatch: - types: [staging-deployment] - -jobs: - graasp-deploy-s3-workflow: - # abort previous deployment if a newer one is in progress - concurrency: - group: deploy-staging - cancel-in-progress: true - - name: Graasp Builder - uses: graasp/graasp-deploy/.github/workflows/cdelivery-s3.yml@v1 - with: - build-folder: 'build' - tag: ${{ github.event.client_payload.tag }} - secrets: - api-host: ${{ secrets.REACT_APP_API_HOST_STAGE }} - authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_STAGE }} - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_STAGE }} - aws-region: ${{ secrets.AWS_REGION_STAGE }} - aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_STAGE }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGE }} - cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_STAGE }} - domain: ${{ secrets.REACT_APP_DOMAIN_STAGE }} - ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_STAGE }} - graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_STAGE }} - graasp-assets-url: ${{ secrets.REACT_APP_GRAASP_ASSETS_URL_STAGE }} - graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_STAGE }} - graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_STAGE }} - h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_STAGE }} - sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }} - show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }} diff --git a/.github/workflows/cdeployment-s3-caller.yml b/.github/workflows/cdeployment-s3-caller.yml deleted file mode 100644 index fb0f2c2cc..000000000 --- a/.github/workflows/cdeployment-s3-caller.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Deploy to production environment - -# Controls when the action will run -on: - # Triggers the workflow on repository-dispatch event - repository_dispatch: - types: [production-deployment] - -# This workflow is made up of one job that calls the reusable workflow in graasp-deploy -jobs: - graasp-deploy-s3-workflow: - # abort previous deployment if a newer one is in progress - concurrency: - group: deploy-production - cancel-in-progress: true - - # Replace with repository name - name: Graasp Builder - # Replace 'main' with the hash of a commit, so it points to an specific version of the reusable workflow that is used - # Reference reusable workflow file. Using the commit SHA is the safest for stability and security - uses: graasp/graasp-deploy/.github/workflows/cdeployment-s3.yml@v1 - # Replace input build-folder if needed. - with: - build-folder: 'build' - tag: ${{ github.event.client_payload.tag }} - # Insert required secrets based on repository with the following format: ${{ secrets.SECRET_NAME }} - secrets: - api-host: ${{ secrets.REACT_APP_API_HOST_PROD }} - authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_PROD }} - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }} - aws-region: ${{ secrets.AWS_REGION_PROD }} - aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_PROD }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }} - cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_PROD }} - domain: ${{ secrets.REACT_APP_DOMAIN_PROD }} - ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_PROD }} - graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_PROD }} - graasp-assets-url: ${{ secrets.REACT_APP_GRAASP_ASSETS_URL_PROD }} - graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_PROD }} - graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_PROD }} - h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_PROD }} - sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }} - show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }} diff --git a/.github/workflows/cintegration-s3-caller.yml b/.github/workflows/cintegration-s3-caller.yml deleted file mode 100644 index 2ca345d96..000000000 --- a/.github/workflows/cintegration-s3-caller.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Deploy to development environment - -# Control when the action will run -on: - # Triggers the workflow on push events only for the main branch - push: - branches: - - main - - master - - # Allows to run the workflow manually from the Actions tab - workflow_dispatch: - -# This workflow is made up of one job that calls the reusable workflow in graasp-deploy -jobs: - graasp-deploy-s3-workflow: - # abort previous deployment if a newer one is in progress - concurrency: - group: deploy-development - cancel-in-progress: true - - name: Graasp Builder - # Reference reusable workflow file. Using the commit SHA is the safest for stability and security - uses: graasp/graasp-deploy/.github/workflows/cintegration-s3.yml@v1 - with: - build-folder: 'build' - secrets: - api-host: ${{ secrets.REACT_APP_API_HOST_DEV }} - authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_DEV }} - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEV }} - aws-region: ${{ secrets.AWS_REGION_DEV }} - aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_DEV }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} - cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_DEV }} - domain: ${{ secrets.REACT_APP_DOMAIN_DEV }} - ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_DEV }} - graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_DEV }} - graasp-assets-url: ${{ secrets.REACT_APP_GRAASP_ASSETS_URL_DEV }} - graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_DEV }} - graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_DEV }} - h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_DEV }} - sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }} - show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }} diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index aa83ca882..ea097c110 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -6,25 +6,74 @@ on: push: branches-ignore: - main - - master -# This workflow is made up of one job that calls the reusable workflow in graasp-deploy +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: false + jobs: - graasp-deploy-cypress-workflow: - # only cancel in-progress jobs or runs for the current workflow - concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - cancel-in-progress: true - - # Replace with repository name - name: Cypress caller template - # Replace 'main' with the hash of a commit, so it points to an specific version of the reusable workflow that is used - # Reference reusable workflow file. Using the commit SHA is the safest for stability and security - uses: graasp/graasp-deploy/.github/workflows/cypress.yml@v1 - with: - # Test values - node-env-test: test - hidden-item-tag-id-test: 12345678-1234-1234-1234-123456789012 - # Insert required secrets based on repository with the following format: ${{ secrets.SECRET_NAME }} - secrets: - api-host-test: ${{ secrets.REACT_APP_API_HOST }} + cypress: + name: Cypress + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Yarn Install and Cache + uses: graasp/graasp-deploy/.github/actions/yarn-install-and-cache@v1 + with: + cypress: true + + # type check + - name: Type-check code + run: tsc --noEmit + + - name: Build App + run: NODE_OPTIONS=--max-old-space-size=3072 yarn build:test + shell: bash + env: + VITE_PORT: ${{ vars.VITE_PORT }} + VITE_VERSION: ${{ vars.VITE_VERSION }} + VITE_GRAASP_DOMAIN: ${{ vars.VITE_GRAASP_DOMAIN }} + VITE_GRAASP_API_HOST: ${{ vars.VITE_GRAASP_API_HOST }} + VITE_GRAASP_AUTH_HOST: ${{ vars.VITE_GRAASP_AUTH_HOST }} + VITE_GRAASP_PLAYER_HOST: ${{ vars.VITE_GRAASP_PLAYER_HOST }} + VITE_GRAASP_LIBRARY_HOST: ${{ vars.VITE_GRAASP_LIBRARY_HOST }} + VITE_GRAASP_ANALYZER_HOST: ${{ vars.VITE_GRAASP_ANALYZER_HOST }} + VITE_SHOW_NOTIFICATIONS: ${{ vars.VITE_SHOW_NOTIFICATIONS }} + + + # use the Cypress GitHub Action to run Cypress tests within the chrome browser + - name: Cypress run + uses: cypress-io/github-action@v5 + with: + install: false + # we launch the app in preview mode to avoid issues with hmr websockets from vite polluting the mocks + start: yarn preview:test + browser: chrome + quiet: true + config-file: cypress.config.ts + cache-key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + env: + VITE_PORT: ${{ vars.VITE_PORT }} + VITE_VERSION: ${{ vars.VITE_VERSION }} + VITE_GRAASP_DOMAIN: ${{ vars.VITE_GRAASP_DOMAIN }} + VITE_GRAASP_API_HOST: ${{ vars.VITE_GRAASP_API_HOST }} + VITE_GRAASP_AUTH_HOST: ${{ vars.VITE_GRAASP_AUTH_HOST }} + VITE_GRAASP_PLAYER_HOST: ${{ vars.VITE_GRAASP_PLAYER_HOST }} + VITE_GRAASP_LIBRARY_HOST: ${{ vars.VITE_GRAASP_LIBRARY_HOST }} + VITE_GRAASP_ANALYZER_HOST: ${{ vars.VITE_GRAASP_ANALYZER_HOST }} + VITE_SHOW_NOTIFICATIONS: ${{ vars.VITE_SHOW_NOTIFICATIONS }} + + # after the test run completes + # store any screenshots + # NOTE: screenshots will be generated only if E2E test failed + # thus we store screenshots only on failures + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: cypress-screenshots + path: cypress/screenshots + + - name: coverage report + run: npx nyc report --reporter=text-summary diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 000000000..66a51151d --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,52 @@ +name: Deploy to development environment + +# Control when the action will run +on: + # Triggers the workflow on push events only for the main branch + push: + branches: + - main + + # Allows to run the workflow manually from the Actions tab + workflow_dispatch: + +jobs: + deploy-app: + name: Deploy to dev + runs-on: ubuntu-latest + environment: development + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Yarn install and Cache dependencies + uses: graasp/graasp-deploy/.github/actions/yarn-install-and-cache@v1 + + - name: Yarn build + # Set environment variables required to perform the build. These are only available to this step + env: + VITE_VERSION: ${{ github.sha }} + VITE_GRAASP_DOMAIN: ${{ vars.VITE_GRAASP_DOMAIN }} + VITE_GRAASP_API_HOST: ${{ vars.VITE_GRAASP_API_HOST }} + VITE_GRAASP_AUTH_HOST: ${{ vars.VITE_GRAASP_AUTH_HOST }} + VITE_GRAASP_PLAYER_HOST: ${{ vars.VITE_GRAASP_PLAYER_HOST }} + VITE_GRAASP_LIBRARY_HOST: ${{ vars.VITE_GRAASP_LIBRARY_HOST }} + VITE_GRAASP_ANALYZER_HOST: ${{ vars.VITE_GRAASP_ANALYZER_HOST }} + VITE_H5P_INTEGRATION_URL: ${{ secrets.VITE_H5P_INTEGRATION_URL }} + VITE_SENTRY_ENV: ${{ vars.VITE_SENTRY_ENV }} + VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }} + # VITE_GA_MEASUREMENT_ID: ${{ secrets.VITE_GA_MEASUREMENT_ID }} + VITE_SHOW_NOTIFICATIONS: ${{ vars.VITE_SHOW_NOTIFICATIONS }} + run: yarn build + shell: bash + + - name: Deploy + uses: graasp/graasp-deploy/.github/actions/deploy-s3@v1 + # Replace input build-folder or version if needed + with: + build-folder: 'build' + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEV }} + aws-region: ${{ secrets.AWS_REGION_DEV }} + aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_DEV }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} + cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_DEV }} diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 000000000..915e2f787 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,55 @@ +name: Deploy to production environment + +# Control when the action will run +on: + # Triggers the workflow on repository-dispatch event + repository_dispatch: + types: [production-deployment] + +jobs: + deploy-app: + name: Deploy to production + runs-on: ubuntu-latest + environment: production + + concurrency: + group: deploy-production + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.client_payload.tag }} + + - name: Yarn install and Cache dependencies + uses: graasp/graasp-deploy/.github/actions/yarn-install-and-cache@v1 + + - name: Yarn build + # Set environment variables required to perform the build. These are only available to this step + env: + VITE_VERSION: ${{ github.event.client_payload.tag }} + VITE_GRAASP_DOMAIN: ${{ vars.VITE_GRAASP_DOMAIN }} + VITE_GRAASP_API_HOST: ${{ vars.VITE_GRAASP_API_HOST }} + VITE_GRAASP_AUTH_HOST: ${{ vars.VITE_GRAASP_AUTH_HOST }} + VITE_GRAASP_PLAYER_HOST: ${{ vars.VITE_GRAASP_PLAYER_HOST }} + VITE_GRAASP_LIBRARY_HOST: ${{ vars.VITE_GRAASP_LIBRARY_HOST }} + VITE_GRAASP_ANALYZER_HOST: ${{ vars.VITE_GRAASP_ANALYZER_HOST }} + VITE_H5P_INTEGRATION_URL: ${{ secrets.VITE_H5P_INTEGRATION_URL }} + VITE_SENTRY_ENV: ${{ vars.VITE_SENTRY_ENV }} + VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }} + VITE_GA_MEASUREMENT_ID: ${{ secrets.VITE_GA_MEASUREMENT_ID }} + VITE_SHOW_NOTIFICATIONS: ${{ vars.VITE_SHOW_NOTIFICATIONS }} + run: yarn build + shell: bash + + - name: Deploy + uses: graasp/graasp-deploy/.github/actions/deploy-s3@v1 + # Replace input build-folder or version if needed + with: + build-folder: 'build' + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }} + aws-region: ${{ secrets.AWS_REGION_PROD }} + aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_PROD }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }} + cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_PROD }} diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml new file mode 100644 index 000000000..a813e6b2a --- /dev/null +++ b/.github/workflows/deploy-stage.yml @@ -0,0 +1,56 @@ +name: Deploy to staging environment + +# Control when the action will run +on: + # Triggers the workflow on repository-dispatch event + repository_dispatch: + types: [staging-deployment] + +jobs: + deploy-app: + name: Deploy to stage + runs-on: ubuntu-latest + environment: staging + + concurrency: + group: deploy-staging + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.client_payload.tag }} + + - name: Yarn install and Cache dependencies + uses: graasp/graasp-deploy/.github/actions/yarn-install-and-cache@v1 + + - name: Yarn build + # Set environment variables required to perform the build. These are only available to this step + env: + VITE_VERSION: ${{ github.event.client_payload.tag }} + VITE_GRAASP_DOMAIN: ${{ vars.VITE_GRAASP_DOMAIN }} + VITE_GRAASP_API_HOST: ${{ vars.VITE_GRAASP_API_HOST }} + VITE_GRAASP_AUTH_HOST: ${{ vars.VITE_GRAASP_AUTH_HOST }} + VITE_GRAASP_PLAYER_HOST: ${{ vars.VITE_GRAASP_PLAYER_HOST }} + VITE_GRAASP_LIBRARY_HOST: ${{ vars.VITE_GRAASP_LIBRARY_HOST }} + VITE_GRAASP_ANALYZER_HOST: ${{ vars.VITE_GRAASP_ANALYZER_HOST }} + VITE_H5P_INTEGRATION_URL: ${{ secrets.VITE_H5P_INTEGRATION_URL }} + VITE_SENTRY_ENV: ${{ vars.VITE_SENTRY_ENV }} + VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }} + # un-comment to enable Google Analytics + # VITE_GA_MEASUREMENT_ID: ${{ secrets.VITE_GA_MEASUREMENT_ID }} + VITE_SHOW_NOTIFICATIONS: ${{ vars.VITE_SHOW_NOTIFICATIONS }} + run: yarn build + shell: bash + + - name: Deploy + uses: graasp/graasp-deploy/.github/actions/deploy-s3@v1 + # Replace input build-folder or version if needed + with: + build-folder: 'build' + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_STAGE }} + aws-region: ${{ secrets.AWS_REGION_STAGE }} + aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_STAGE }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGE }} + cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_STAGE }} diff --git a/.github/workflows/update-staging-version.yml b/.github/workflows/update-staging-version.yml deleted file mode 100644 index 5d9facbe1..000000000 --- a/.github/workflows/update-staging-version.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This workflow triggers a new workflow inside the graasp-deploy repository. It passes a json -# with the repository name and the latest tag pushed from the caller repository. -name: Push new tag to graasp-deploy repository - -# Controls when the action will run -on: - # Allow the workflow to be manually triggered - workflow_dispatch: - # Inputs the workflow accepts. - inputs: - release-type: - # Description to be shown in the UI instead of 'stack' - description: 'Select a release type' - # Default value if no value is explicitly provided - # Input does not have to be provided for the workflow to run - type: choice - options: - - first - - patch - - minor - - major - default: patch - required: true - -# This workflow is made up of one job that calls the reusable workflow in graasp-deploy -jobs: - graasp-deploy-update-staging-version-workflow: - # Replace with repository name - name: Graasp Builder - # Replace 'main' with the hash of a commit, so it points to an specific version of the reusable workflow that is used - # Reference reusable workflow file. Using the commit SHA is the safest for stability and security - uses: graasp/graasp-deploy/.github/workflows/update-staging-version.yml@v1 - with: - release-type: ${{ inputs.release-type }} - secrets: - token: ${{ secrets.REPO_ACCESS_TOKEN }} diff --git a/.gitignore b/.gitignore index c3eebcf18..16034d759 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # misc .DS_Store +bundle_analysis.html npm-debug.log* yarn-debug.log* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..26b8dcfb9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +**/cypress/screenshots/**/* diff --git a/.prettierrc b/.prettierrc index fbe7b41da..d3bc3fbb0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,7 +5,14 @@ "singleQuote": true, "bracketSpacing": true, "arrowParens": "always", - "importOrder": ["^@mui", "^react", "^@?graasp", "^[./]"], + "importOrder": [ + "^react", + "^@?mui", + "^@?graasp", + "", + "^@/", + "^[./]" + ], "importOrderSeparation": true, "importOrderSortSpecifiers": true, "plugins": ["@trivago/prettier-plugin-sort-imports"] diff --git a/README.md b/README.md index a6bbda5d1..0ff78ee2b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Graasp Builder + [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) @@ -7,15 +8,15 @@ 1. Run `yarn` to install the dependencies. 2. Run the API at `localhost:3000` -3. Set the following environnement variables in `.env.local` - -``` -REACT_APP_API_HOST=http://localhost:3000 -PORT=3111 -REACT_APP_SHOW_NOTIFICATIONS=true -REACT_APP_AUTHENTICATION_HOST=http://localhost:3001 -REACT_APP_H5P_INTEGRATION_URL= -REACT_APP_VERSION=latest +3. Set the following environnement variables in `.env.development` + +```sh +VITE_PORT=3111 +VITE_GRAASP_API_HOST=http://localhost:3000 +VITE_SHOW_NOTIFICATIONS=true +VITE_GRAASP_AUTH_HOST=http://localhost:3001 +VITE_H5P_INTEGRATION_URL= +VITE_VERSION=latest-dev ``` 4. Run `yarn start`. The client should be accessible at `localhost:3111` @@ -34,19 +35,37 @@ You are successfully authenticated if you can access to the client without being ## Testing +The tests are run using Cypress. Cypress only compiles the code for the tests, your app needs to run at the specified `baseUrl` in the cypress config. + +### Running tests in interactive mode + Set the following environnement variables in `.env.test` +```sh +VITE_PORT=3333 +VITE_GRAASP_API_HOST=http://localhost:3000 +VITE_GRAASP_AUTH_HOST=http://localhost:3001 +VITE_GRAASP_PLAYER_HOST=http://localhost:3112 +VITE_GRAASP_LIBRARY_HOST=http://localhost:3005 +VITE_GRAASP_ANALYZER_HOST=http://localhost:3113 +VITE_H5P_INTEGRATION_URL= +VITE_VERSION=cypress-tests +VITE_SHOW_NOTIFICATIONS=true ``` -REACT_APP_API_HOST=http://localhost:3000 -PORT=3111 -REACT_APP_SHOW_NOTIFICATIONS=false -REACT_APP_NODE_ENV=test -REACT_APP_H5P_INTEGRATION_URL= -REACT_APP_VERSION=latest -``` -Run `yarn cypress`. This should run every tests headlessly. -You can run `yarn cypress:open` to access the framework and visually display the tests' processes. +Run `yarn start:test` and `yarn cypress:open` in 2 terminal windows. + +:warning: It is possible that the websocket test become flacks (or just stop passing) if you use the dev server. In that case, you can resort to first building the app in test mode `yarn build:test` and then starting a preview of the app with `yarn preview:test`. + +### Running all tests in headless mode + +You will need to have the `.env.test` file from the other section. + +You can simply run: `yarn test`. This will: + +1. Build your app in test mode (using the `.env.test` file to pull env variables) +2. Start your app in preview mode (simply serve the generated files with a static http server, but using the same `.env.test` file) +3. Start the cypress tests to run your full test suite (this can take a while depending on the number of tests you have) ## Developing @@ -71,10 +90,3 @@ While developing you can run `yarn check` to perform prettier formatting checks, - - - - - - - diff --git a/cypress.config.ts b/cypress.config.ts index 079cb86e5..b5fc2c9ee 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-extraneous-dependencies import { defineConfig } from 'cypress'; export default defineConfig({ @@ -8,12 +7,20 @@ export default defineConfig({ }, chromeWebSecurity: false, e2e: { + env: { + API_HOST: process.env.VITE_GRAASP_API_HOST, + AUTH_HOST: process.env.VITE_GRAASP_AUTH_HOST, + BUILDER_HOST: `http://localhost:${process.env.VITE_PORT}`, + PLAYER_HOST: process.env.VITE_GRAASP_PLAYER_HOST, + ANALYZER_HOST: process.env.VITE_GRAASP_ANALYZER_HOST, + }, // We've imported your old cypress plugins here. // You may want to clean this up later by importing these. setupNodeEvents(on, config) { - // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires, import/extensions - return require('./cypress/plugins/index.ts')(on, config); + // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require + require('@cypress/code-coverage/task')(on, config); + return config; }, - baseUrl: 'http://localhost:3111', + baseUrl: `http://localhost:${process.env.VITE_PORT || 3333}`, }, }); diff --git a/cypress/e2e/authentication.cy.ts b/cypress/e2e/authentication.cy.ts index 813c0a24d..c021481de 100644 --- a/cypress/e2e/authentication.cy.ts +++ b/cypress/e2e/authentication.cy.ts @@ -1,6 +1,5 @@ import { COOKIE_KEYS } from '@graasp/sdk'; -import { SIGN_IN_PATH } from '../../src/config/constants'; import { HOME_PATH, ITEMS_PATH, @@ -16,10 +15,10 @@ import { import { SAMPLE_ITEMS } from '../fixtures/items'; import { SIGNED_OUT_MEMBER } from '../fixtures/members'; import { - PAGE_LOAD_WAITING_PAUSE, REDIRECTION_TIME, REQUEST_FAILURE_LOADING_TIME, } from '../support/constants'; +import { SIGN_IN_PATH } from '../support/paths'; describe('Authentication', () => { describe('Signed Off > Redirect to sign in route', () => { @@ -28,23 +27,17 @@ describe('Authentication', () => { }); it('Home', () => { cy.visit(HOME_PATH); - cy.wait(REQUEST_FAILURE_LOADING_TIME); - cy.getCookie(COOKIE_KEYS.REDIRECT_URL_KEY).should( - 'have.property', - 'value', - HOME_PATH, - ); cy.url().should('equal', SIGN_IN_PATH); + cy.getCookie(COOKIE_KEYS.REDIRECT_URL_KEY, { + timeout: REQUEST_FAILURE_LOADING_TIME, + }).should('have.property', 'value', HOME_PATH); }); it('Shared Items', () => { cy.visit(SHARED_ITEMS_PATH); - cy.wait(REQUEST_FAILURE_LOADING_TIME); - cy.getCookie(COOKIE_KEYS.REDIRECT_URL_KEY).should( - 'have.property', - 'value', - SHARED_ITEMS_PATH, - ); cy.url().should('equal', SIGN_IN_PATH); + cy.getCookie(COOKIE_KEYS.REDIRECT_URL_KEY, { + timeout: REQUEST_FAILURE_LOADING_TIME, + }).should('have.property', 'value', SHARED_ITEMS_PATH); }); }); @@ -56,12 +49,10 @@ describe('Authentication', () => { describe('Load page correctly', () => { it('Home', () => { cy.visit(HOME_PATH); - cy.wait(PAGE_LOAD_WAITING_PAUSE); cy.get(`#${HEADER_APP_BAR_ID}`).should('exist'); }); it('Shared Items', () => { cy.visit(SHARED_ITEMS_PATH); - cy.wait(PAGE_LOAD_WAITING_PAUSE); cy.get(`#${HEADER_APP_BAR_ID}`).should('exist'); cy.get(`#${CREATE_ITEM_BUTTON_ID}`).should('not.exist'); }); @@ -76,22 +67,25 @@ describe('Authentication', () => { it('Home', () => { cy.setCookie(COOKIE_KEYS.REDIRECT_URL_KEY, HOME_PATH); cy.visit(REDIRECT_PATH); - cy.wait(REDIRECTION_TIME); - cy.url().should('include', HOME_PATH); + cy.url({ + timeout: REDIRECTION_TIME, + }).should('include', HOME_PATH); }); it('Items', () => { cy.setCookie(COOKIE_KEYS.REDIRECT_URL_KEY, ITEMS_PATH); cy.visit(REDIRECT_PATH); - cy.wait(REDIRECTION_TIME); - cy.url().should('include', ITEMS_PATH); + cy.url({ + timeout: REDIRECTION_TIME, + }).should('include', ITEMS_PATH); }); it('SharedItems', () => { cy.setCookie(COOKIE_KEYS.REDIRECT_URL_KEY, SHARED_ITEMS_PATH); cy.visit(REDIRECT_PATH); - cy.wait(REDIRECTION_TIME); - cy.url().should('include', SHARED_ITEMS_PATH); + cy.url({ + timeout: REDIRECTION_TIME, + }).should('include', SHARED_ITEMS_PATH); }); it('Item', () => { @@ -100,8 +94,10 @@ describe('Authentication', () => { buildItemPath(SAMPLE_ITEMS.items?.[0].id), ); cy.visit(REDIRECT_PATH); - cy.wait(REDIRECTION_TIME); - cy.url().should('include', buildItemPath(SAMPLE_ITEMS.items?.[0].id)); + cy.url({ timeout: REDIRECTION_TIME }).should( + 'include', + buildItemPath(SAMPLE_ITEMS.items?.[0].id), + ); }); }); }); diff --git a/cypress/e2e/header.cy.ts b/cypress/e2e/header.cy.ts index 28d0b48e0..3d1792aff 100644 --- a/cypress/e2e/header.cy.ts +++ b/cypress/e2e/header.cy.ts @@ -1,4 +1,3 @@ -import { SIGN_IN_PATH } from '../../src/config/constants'; import { HOME_PATH } from '../../src/config/paths'; import { APP_NAVIGATION_PLATFORM_SWITCH_BUTTON_IDS, @@ -8,6 +7,7 @@ import { HEADER_MEMBER_MENU_SIGN_OUT_BUTTON_ID, MEMBER_PROFILE_MEMBER_NAME_ID, } from '../../src/config/selectors'; +import { SIGN_IN_PATH } from '../support/paths'; describe('Header', () => { it('App Navigation', () => { diff --git a/cypress/e2e/invitations/createInvitation.cy.ts b/cypress/e2e/invitations/createInvitation.cy.ts index 1b10c2ec7..983c8d2e1 100644 --- a/cypress/e2e/invitations/createInvitation.cy.ts +++ b/cypress/e2e/invitations/createInvitation.cy.ts @@ -35,7 +35,7 @@ describe('Create Invitation', () => { it('invite one new member', () => { cy.setUpApi({ ...SAMPLE_ITEMS, members: Object.values(MEMBERS) }); - const { id } = SAMPLE_ITEMS.items?.[0]; + const id = SAMPLE_ITEMS.items?.[0].id; cy.visit(buildItemPath(id)); // invite diff --git a/cypress/e2e/invitations/deleteInvitation.cy.ts b/cypress/e2e/invitations/deleteInvitation.cy.ts index 8e8c47e1d..fa5bc2477 100644 --- a/cypress/e2e/invitations/deleteInvitation.cy.ts +++ b/cypress/e2e/invitations/deleteInvitation.cy.ts @@ -5,7 +5,7 @@ import { } from '../../../src/config/selectors'; import { ITEMS_WITH_INVITATIONS } from '../../fixtures/invitations'; -const deleteInvitation = ({ id, itemId }) => { +const deleteInvitation = ({ id, itemId }: { id: string; itemId: string }) => { cy.get(`#${buildShareButtonId(itemId)}`).click(); cy.get(`#${buildItemInvitationRowDeleteButtonId(id)}`).click(); }; diff --git a/cypress/e2e/invitations/editInvitation.cy.ts b/cypress/e2e/invitations/editInvitation.cy.ts index d45936dba..4cfd2993a 100644 --- a/cypress/e2e/invitations/editInvitation.cy.ts +++ b/cypress/e2e/invitations/editInvitation.cy.ts @@ -9,7 +9,15 @@ import { } from '../../../src/config/selectors'; import { ITEMS_WITH_INVITATIONS } from '../../fixtures/invitations'; -const editInvitation = ({ itemId, id, permission }) => { +const editInvitation = ({ + itemId, + id, + permission, +}: { + itemId: string; + id: string; + permission: PermissionLevel; +}) => { cy.get(`#${buildShareButtonId(itemId)}`).click(); const select = cy.get( `${buildInvitationTableRowSelector( diff --git a/cypress/e2e/item/analytics/analytics.cy.ts b/cypress/e2e/item/analytics/analytics.cy.ts index 3321d2cbe..82f7d2865 100644 --- a/cypress/e2e/item/analytics/analytics.cy.ts +++ b/cypress/e2e/item/analytics/analytics.cy.ts @@ -5,7 +5,7 @@ import { } from '../../../../src/config/selectors'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; -const openAnalyticsDashboard = (itemId) => { +const openAnalyticsDashboard = (itemId: string) => { cy.get(`#${buildDashboardButtonId(itemId)}`).click(); }; diff --git a/cypress/e2e/item/chatbox/chatbox.cy.ts b/cypress/e2e/item/chatbox/chatbox.cy.ts index c7dbe8e55..e1c7997b8 100644 --- a/cypress/e2e/item/chatbox/chatbox.cy.ts +++ b/cypress/e2e/item/chatbox/chatbox.cy.ts @@ -23,7 +23,7 @@ const openChatbox = () => { }; describe('Chatbox Scenarios', () => { - let client; + let client: MockWebSocket; beforeEach(() => { client = new MockWebSocket(); diff --git a/cypress/e2e/item/copy/gridCopyItem.cy.ts b/cypress/e2e/item/copy/gridCopyItem.cy.ts index 42da6acb2..5dc690706 100644 --- a/cypress/e2e/item/copy/gridCopyItem.cy.ts +++ b/cypress/e2e/item/copy/gridCopyItem.cy.ts @@ -9,7 +9,7 @@ import { import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; -const copyItem = ({ id, toItemPath }) => { +const copyItem = ({ id, toItemPath }: { id: string; toItemPath: string }) => { const menuSelector = `#${buildItemMenuButtonId(id)}`; cy.get(menuSelector).click(); cy.get(`#${buildItemMenu(id)} .${ITEM_MENU_COPY_BUTTON_CLASS}`).click(); diff --git a/cypress/e2e/item/copy/listCopyMultiple.cy.ts b/cypress/e2e/item/copy/listCopyMultiple.cy.ts index 1f62d3d34..09873f4a0 100644 --- a/cypress/e2e/item/copy/listCopyMultiple.cy.ts +++ b/cypress/e2e/item/copy/listCopyMultiple.cy.ts @@ -7,7 +7,13 @@ import { import ITEM_LAYOUT_MODES from '../../../../src/enums/itemLayoutModes'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; -const copyItems = ({ itemIds, toItemPath }) => { +const copyItems = ({ + itemIds, + toItemPath, +}: { + itemIds: string[]; + toItemPath: string; +}) => { // check selected ids itemIds.forEach((id) => { cy.get(`${buildItemsTableRowIdAttribute(id)} input`).click(); diff --git a/cypress/e2e/item/create/createShortcut.cy.ts b/cypress/e2e/item/create/createShortcut.cy.ts index 8fc995f2d..a6c93831c 100644 --- a/cypress/e2e/item/create/createShortcut.cy.ts +++ b/cypress/e2e/item/create/createShortcut.cy.ts @@ -1,7 +1,7 @@ -import * as qs from 'qs'; - import { ItemType } from '@graasp/sdk'; +import * as qs from 'qs'; + import { HOME_PATH } from '../../../../src/config/paths'; import { ITEM_MENU_SHORTCUT_BUTTON_CLASS, @@ -14,7 +14,13 @@ import { IMAGE_ITEM_DEFAULT } from '../../../fixtures/files'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const createShortcut = ({ id, toItemPath }) => { +const createShortcut = ({ + id, + toItemPath, +}: { + id: string; + toItemPath: string; +}) => { cy.get(`#${buildItemMenu(id)} .${ITEM_MENU_SHORTCUT_BUTTON_CLASS}`).click(); cy.fillTreeModal(toItemPath); }; diff --git a/cypress/e2e/item/delete/gridRecycleItem.cy.ts b/cypress/e2e/item/delete/gridRecycleItem.cy.ts index 425966583..d69bf1833 100644 --- a/cypress/e2e/item/delete/gridRecycleItem.cy.ts +++ b/cypress/e2e/item/delete/gridRecycleItem.cy.ts @@ -9,7 +9,7 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const recycleItem = (id) => { +const recycleItem = (id: string) => { const menuSelector = `#${buildItemMenuButtonId(id)}`; cy.wait(TABLE_ITEM_RENDER_TIME); cy.get(menuSelector).click(); diff --git a/cypress/e2e/item/delete/listDeleteItem.cy.ts b/cypress/e2e/item/delete/listDeleteItem.cy.ts index fb167a0af..e95a9ac02 100644 --- a/cypress/e2e/item/delete/listDeleteItem.cy.ts +++ b/cypress/e2e/item/delete/listDeleteItem.cy.ts @@ -8,7 +8,7 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { RECYCLED_ITEM_DATA, SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const deleteItem = (id) => { +const deleteItem = (id: string) => { cy.wait(TABLE_ITEM_RENDER_TIME); cy.get( `${buildItemsTableRowIdAttribute(id)} .${ITEM_DELETE_BUTTON_CLASS}`, diff --git a/cypress/e2e/item/delete/listDeleteItems.cy.ts b/cypress/e2e/item/delete/listDeleteItems.cy.ts index 8e54bb55c..b7b0d0f32 100644 --- a/cypress/e2e/item/delete/listDeleteItems.cy.ts +++ b/cypress/e2e/item/delete/listDeleteItems.cy.ts @@ -8,7 +8,7 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { RECYCLED_ITEM_DATA, SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const deleteItems = (itemIds) => { +const deleteItems = (itemIds: string[]) => { // check selected ids itemIds.forEach((id) => { cy.wait(TABLE_ITEM_RENDER_TIME); diff --git a/cypress/e2e/item/delete/listRecycleItem.cy.ts b/cypress/e2e/item/delete/listRecycleItem.cy.ts index 09fea1838..b6dab14c3 100644 --- a/cypress/e2e/item/delete/listRecycleItem.cy.ts +++ b/cypress/e2e/item/delete/listRecycleItem.cy.ts @@ -9,7 +9,7 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const recycleItem = (id) => { +const recycleItem = (id: string) => { const menuSelector = `#${buildItemMenuButtonId(id)}`; cy.wait(TABLE_ITEM_RENDER_TIME); cy.get(menuSelector).click(); diff --git a/cypress/e2e/item/delete/listRecycleItems.cy.ts b/cypress/e2e/item/delete/listRecycleItems.cy.ts index 6057c3124..dda7c226a 100644 --- a/cypress/e2e/item/delete/listRecycleItems.cy.ts +++ b/cypress/e2e/item/delete/listRecycleItems.cy.ts @@ -7,7 +7,7 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const recycleItems = (itemIds) => { +const recycleItems = (itemIds: string[]) => { // check selected ids itemIds.forEach((id) => { cy.wait(TABLE_ITEM_RENDER_TIME); diff --git a/cypress/e2e/item/delete/listRestoreItem.cy.ts b/cypress/e2e/item/delete/listRestoreItem.cy.ts index 65c92a3c7..f51a2eaed 100644 --- a/cypress/e2e/item/delete/listRestoreItem.cy.ts +++ b/cypress/e2e/item/delete/listRestoreItem.cy.ts @@ -8,14 +8,14 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { RECYCLED_ITEM_DATA, SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const restoreItem = (id) => { +const restoreItem = (id: string) => { cy.wait(TABLE_ITEM_RENDER_TIME); cy.get( `${buildItemsTableRowIdAttribute(id)} .${RESTORE_ITEMS_BUTTON_CLASS}`, ).click(); }; -const restoreItems = (itemIds) => { +const restoreItems = (itemIds: string[]) => { // check selected ids itemIds.forEach((id) => { cy.wait(TABLE_ITEM_RENDER_TIME); diff --git a/cypress/e2e/item/favorite/favoriteItem.cy.ts b/cypress/e2e/item/favorite/favoriteItem.cy.ts index 20a59cdce..20fe27d34 100644 --- a/cypress/e2e/item/favorite/favoriteItem.cy.ts +++ b/cypress/e2e/item/favorite/favoriteItem.cy.ts @@ -12,7 +12,7 @@ import { SAMPLE_FAVORITE, SAMPLE_ITEMS } from '../../../fixtures/items'; import { CURRENT_USER } from '../../../fixtures/members'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const toggleFavoriteButton = (itemId) => { +const toggleFavoriteButton = (itemId: string) => { cy.wait(TABLE_ITEM_RENDER_TIME); const menuSelector = `#${buildItemMenuButtonId(itemId)}`; cy.get(menuSelector).click(); diff --git a/cypress/e2e/item/flag/flagItem.cy.ts b/cypress/e2e/item/flag/flagItem.cy.ts index 1789cb046..244804d3a 100644 --- a/cypress/e2e/item/flag/flagItem.cy.ts +++ b/cypress/e2e/item/flag/flagItem.cy.ts @@ -12,7 +12,7 @@ import { import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { CURRENT_USER } from '../../../fixtures/members'; -const openFlagItemModal = (itemId) => { +const openFlagItemModal = (itemId: string) => { const menuSelector = `#${buildItemMenuButtonId(itemId)}`; cy.get(menuSelector).click(); @@ -23,7 +23,7 @@ const openFlagItemModal = (itemId) => { menuFlagButton.click(); }; -const flagItem = (itemId, type) => { +const flagItem = (itemId: string, type: FlagType) => { openFlagItemModal(itemId); const flagListItem = cy.get(`#${buildFlagListItemId(type)}`); diff --git a/cypress/e2e/item/hide/hideItem.cy.ts b/cypress/e2e/item/hide/hideItem.cy.ts index 035a67c76..a273f913e 100644 --- a/cypress/e2e/item/hide/hideItem.cy.ts +++ b/cypress/e2e/item/hide/hideItem.cy.ts @@ -14,7 +14,7 @@ import { ITEMS_SETTINGS, } from '../../../fixtures/items'; -const toggleHideButton = (itemId, isHidden = false) => { +const toggleHideButton = (itemId: string, isHidden = false) => { const menuSelector = `#${buildItemMenuButtonId(itemId)}`; cy.get(menuSelector).click(); diff --git a/cypress/e2e/item/move/gridMoveItem.cy.ts b/cypress/e2e/item/move/gridMoveItem.cy.ts index e3d5f0888..b948c646b 100644 --- a/cypress/e2e/item/move/gridMoveItem.cy.ts +++ b/cypress/e2e/item/move/gridMoveItem.cy.ts @@ -9,7 +9,13 @@ import { import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; -const moveItem = ({ id: movedItemId, toItemPath }) => { +const moveItem = ({ + id: movedItemId, + toItemPath, +}: { + id: string; + toItemPath: string; +}) => { const menuSelector = `#${buildItemMenuButtonId(movedItemId)}`; cy.get(menuSelector).click(); cy.get( diff --git a/cypress/e2e/item/move/listMoveMultiple.cy.ts b/cypress/e2e/item/move/listMoveMultiple.cy.ts index e226ec8f1..5e505f641 100644 --- a/cypress/e2e/item/move/listMoveMultiple.cy.ts +++ b/cypress/e2e/item/move/listMoveMultiple.cy.ts @@ -8,7 +8,13 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const moveItems = ({ itemIds, toItemPath }) => { +const moveItems = ({ + itemIds, + toItemPath, +}: { + itemIds: string[]; + toItemPath: string; +}) => { // check selected ids itemIds.forEach((id) => { cy.wait(TABLE_ITEM_RENDER_TIME); diff --git a/cypress/e2e/item/order/reorderItems.cy.ts b/cypress/e2e/item/order/reorderItems.cy.ts index 8626284d7..233b8b258 100644 --- a/cypress/e2e/item/order/reorderItems.cy.ts +++ b/cypress/e2e/item/order/reorderItems.cy.ts @@ -8,7 +8,11 @@ import { import { ITEM_REORDER_ITEMS } from '../../../fixtures/items'; import { ROW_HEIGHT } from '../../../support/constants'; -const reorderAndCheckItem = (id, currentPosition, newPosition) => { +const reorderAndCheckItem = ( + id: string, + currentPosition: number, + newPosition: number, +) => { const dragIcon = `${buildItemsTableRowSelector( id, )} .${ROW_DRAGGER_CLASS} svg`; diff --git a/cypress/e2e/item/pin/pinItem.cy.ts b/cypress/e2e/item/pin/pinItem.cy.ts index 2e73244a8..37ccf0156 100644 --- a/cypress/e2e/item/pin/pinItem.cy.ts +++ b/cypress/e2e/item/pin/pinItem.cy.ts @@ -8,7 +8,7 @@ import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { ITEMS_SETTINGS, PINNED_ITEM } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; -const togglePinButton = (itemId) => { +const togglePinButton = (itemId: string) => { cy.wait(TABLE_ITEM_RENDER_TIME); const menuSelector = `#${buildItemMenuButtonId(itemId)}`; cy.get(menuSelector).click(); diff --git a/cypress/e2e/item/publish/categories.cy.ts b/cypress/e2e/item/publish/categories.cy.ts index 817c21193..bea6e8292 100644 --- a/cypress/e2e/item/publish/categories.cy.ts +++ b/cypress/e2e/item/publish/categories.cy.ts @@ -1,4 +1,4 @@ -import { CategoryType } from '@graasp/sdk'; +import { Category, CategoryType } from '@graasp/sdk'; import { buildItemPath } from '../../../../src/config/paths'; import { @@ -22,7 +22,10 @@ const openPublishItemTab = (id: string) => { cy.wait(PUBLISH_TAB_LOADING_TIME); }; -const toggleOption = (id, categoryType) => { +const toggleOption = ( + id: string, + categoryType: CategoryType | `${CategoryType}`, +) => { cy.get(`#${buildCategorySelectionId(categoryType)}`).click(); cy.get(`#${buildCategorySelectionOptionId(categoryType, id)}`).click(); @@ -31,7 +34,7 @@ const toggleOption = (id, categoryType) => { describe('Categories', () => { describe('Item without category', () => { it('Display item without category', () => { - const item = { ...ITEM_WITH_CATEGORIES, categories: [] }; + const item = { ...ITEM_WITH_CATEGORIES, categories: [] as Category[] }; cy.setUpApi({ items: [item] }); cy.visit(buildItemPath(item.id)); openPublishItemTab(item.id); diff --git a/cypress/e2e/item/publish/ccLicense.cy.ts b/cypress/e2e/item/publish/ccLicense.cy.ts index d600b0c74..736443c48 100644 --- a/cypress/e2e/item/publish/ccLicense.cy.ts +++ b/cypress/e2e/item/publish/ccLicense.cy.ts @@ -10,18 +10,19 @@ import { buildPublishButtonId, } from '../../../../src/config/selectors'; import { PUBLISHED_ITEMS_WITH_CC_LICENSE } from '../../../fixtures/items'; +import { ItemForTest } from '../../../support/types'; -const openPublishItemTab = (id) => { +const openPublishItemTab = (id: string) => { cy.get(`#${buildPublishButtonId(id)}`).click(); }; -const visitItemPage = (item) => { +const visitItemPage = (item: ItemForTest) => { cy.setUpApi({ items: [item] }); cy.visit(buildItemPath(item.id)); openPublishItemTab(item.id); }; -const ensureRadioCheckedState = (parentId, shouldBeChecked) => +const ensureRadioCheckedState = (parentId: string, shouldBeChecked: boolean) => cy .get(`#${parentId}`) // MUI doesn't update the `checked` attribute of checkboxes. diff --git a/cypress/e2e/item/publish/coEditorSettings.cy.ts b/cypress/e2e/item/publish/coEditorSettings.cy.ts index b6d1f0b0b..809b75691 100644 --- a/cypress/e2e/item/publish/coEditorSettings.cy.ts +++ b/cypress/e2e/item/publish/coEditorSettings.cy.ts @@ -15,7 +15,7 @@ import { MEMBERS, SIGNED_OUT_MEMBER } from '../../../fixtures/members'; import { EDIT_TAG_REQUEST_TIMEOUT } from '../../../support/constants'; import { changeVisibility } from '../share/shareItem.cy'; -const openPublishItemTab = (id) => { +const openPublishItemTab = (id: string) => { cy.get(`#${buildPublishButtonId(id)}`).click(); }; diff --git a/cypress/e2e/item/publish/publishedItem.cy.ts b/cypress/e2e/item/publish/publishedItem.cy.ts index 43df50422..059ae759d 100644 --- a/cypress/e2e/item/publish/publishedItem.cy.ts +++ b/cypress/e2e/item/publish/publishedItem.cy.ts @@ -14,7 +14,7 @@ import { import { MEMBERS } from '../../../fixtures/members'; import { PAGE_LOAD_WAITING_PAUSE } from '../../../support/constants'; -const openPublishItemTab = (id) => { +const openPublishItemTab = (id: string) => { cy.get(`#${buildPublishButtonId(id)}`).click(); }; diff --git a/cypress/e2e/item/publish/tags.cy.ts b/cypress/e2e/item/publish/tags.cy.ts index bc9191317..097b89ddd 100644 --- a/cypress/e2e/item/publish/tags.cy.ts +++ b/cypress/e2e/item/publish/tags.cy.ts @@ -14,12 +14,13 @@ import { import { PUBLISHED_ITEM } from '../../../fixtures/items'; import { MEMBERS, SIGNED_OUT_MEMBER } from '../../../fixtures/members'; import { EDIT_TAG_REQUEST_TIMEOUT } from '../../../support/constants'; +import { ItemForTest } from '../../../support/types'; -const openPublishItemTab = (id) => { +const openPublishItemTab = (id: string) => { cy.get(`#${buildPublishButtonId(id)}`).click(); }; -const visitItemPage = (item) => { +const visitItemPage = (item: ItemForTest) => { cy.setUpApi(ITEM_WITH_CATEGORIES_CONTEXT); cy.visit(buildItemPath(item.id)); openPublishItemTab(item.id); diff --git a/cypress/e2e/item/share/itemLogin.cy.ts b/cypress/e2e/item/share/itemLogin.cy.ts index 8cd551c47..a6602b922 100644 --- a/cypress/e2e/item/share/itemLogin.cy.ts +++ b/cypress/e2e/item/share/itemLogin.cy.ts @@ -1,7 +1,7 @@ -import { v4 as uuidv4 } from 'uuid'; - import { ItemLoginSchemaType } from '@graasp/sdk'; +import { v4 as uuidv4 } from 'uuid'; + import { SETTINGS, SETTINGS_ITEM_LOGIN_DEFAULT, @@ -21,13 +21,15 @@ import { ITEM_LOGIN_ITEMS } from '../../../fixtures/items'; import { MEMBERS, SIGNED_OUT_MEMBER } from '../../../fixtures/members'; import { ITEM_LOGIN_PAUSE } from '../../../support/constants'; -const changeSignInMode = (mode) => { +const changeSignInMode = (mode: string) => { cy.get(`#${ITEM_LOGIN_SIGN_IN_MODE_ID}`).click(); cy.get(`li[data-value="${mode}"]`).click(); }; const checkItemLoginScreenLayout = ( - itemLoginSchema = SETTINGS_ITEM_LOGIN_DEFAULT, + itemLoginSchema: + | ItemLoginSchemaType + | `${ItemLoginSchemaType}` = SETTINGS_ITEM_LOGIN_DEFAULT, ) => { cy.get(`#${ITEM_LOGIN_SIGN_IN_USERNAME_ID}`).should('exist'); if (itemLoginSchema === ItemLoginSchemaType.UsernameAndPassword) { @@ -59,7 +61,15 @@ const fillItemLoginScreenLayout = ({ cy.get(`#${ITEM_LOGIN_SIGN_IN_BUTTON_ID}`).click(); }; -const checkItemLoginSetting = ({ isEnabled, mode, disabled = false }) => { +const checkItemLoginSetting = ({ + isEnabled, + mode, + disabled = false, +}: { + isEnabled: boolean; + mode: string; + disabled?: boolean; +}) => { if (isEnabled && !disabled) { cy.get(`#${SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID} + input`).should( 'have.value', @@ -74,7 +84,7 @@ const checkItemLoginSetting = ({ isEnabled, mode, disabled = false }) => { } }; -const editItemLoginSetting = (mode) => { +const editItemLoginSetting = (mode: string) => { cy.get(`#${SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID}`).click(); cy.get(`li[data-value="${mode}"]`).click(); cy.wait('@putItemLoginSchema').then(({ request: { body } }) => { @@ -102,7 +112,7 @@ describe('Item Login', () => { it('username or member id', () => { cy.visit(buildItemPath(ITEM_LOGIN_ITEMS.items[0].id)); checkItemLoginScreenLayout( - ITEM_LOGIN_ITEMS.items[0].itemLoginSchema[0], + ITEM_LOGIN_ITEMS.items[0].itemLoginSchema.type, ); fillItemLoginScreenLayout({ username: 'username', @@ -124,7 +134,7 @@ describe('Item Login', () => { it('username or member id and password', () => { cy.visit(buildItemPath(ITEM_LOGIN_ITEMS.items[3].id)); checkItemLoginScreenLayout( - ITEM_LOGIN_ITEMS.items[3].itemLoginSchema[0], + ITEM_LOGIN_ITEMS.items[3].itemLoginSchema.type, ); fillItemLoginScreenLayout({ username: 'username', diff --git a/cypress/e2e/item/share/shareItem.cy.ts b/cypress/e2e/item/share/shareItem.cy.ts index 571597fe7..ba816beac 100644 --- a/cypress/e2e/item/share/shareItem.cy.ts +++ b/cypress/e2e/item/share/shareItem.cy.ts @@ -1,11 +1,8 @@ import { Context, ItemLoginSchemaType, ItemTagType } from '@graasp/sdk'; +import { buildItemPath } from '@/config/paths'; + import { SETTINGS } from '../../../../src/config/constants'; -import { - buildGraaspBuilderView, - buildGraaspPlayerView, - buildItemPath, -} from '../../../../src/config/paths'; import { SHARE_ITEM_DIALOG_LINK_ID, SHARE_ITEM_DIALOG_LINK_SELECT_ID, @@ -18,16 +15,19 @@ import { SAMPLE_ITEMS, SAMPLE_PUBLIC_ITEMS, } from '../../../fixtures/items'; +import { + buildGraaspBuilderView, + buildGraaspPlayerView, +} from '../../../support/paths'; -const openShareItemTab = (id) => { +const openShareItemTab = (id: string) => { cy.get(`#${buildShareButtonId(id)}`).click(); }; // eslint-disable-next-line import/prefer-default-export export const changeVisibility = (value: string): void => { cy.get(`#${SHARE_ITEM_VISIBILITY_SELECT_ID}`).click(); - cy.wait(1000); - cy.get(`li[data-value="${value}"]`).click(); + cy.get(`li[data-value="${value}"]`, { timeout: 1000 }).click(); }; describe('Share Item', () => { @@ -87,7 +87,6 @@ describe('Share Item', () => { }, ); // change public -> item login - cy.wait(1000); changeVisibility(SETTINGS.ITEM_LOGIN.name); cy.wait([ `@deleteItemTag-${ItemTagType.Public}`, diff --git a/cypress/e2e/item/share/shareItemFromCsv.cy.ts b/cypress/e2e/item/share/shareItemFromCsv.cy.ts index d648137e5..4e3688f42 100644 --- a/cypress/e2e/item/share/shareItemFromCsv.cy.ts +++ b/cypress/e2e/item/share/shareItemFromCsv.cy.ts @@ -1,3 +1,5 @@ +import { PermissionLevel } from '@graasp/sdk'; + import * as Papa from 'papaparse'; import { buildItemPath } from '../../../../src/config/paths'; @@ -12,7 +14,7 @@ import { ITEMS_WITH_INVITATIONS } from '../../../fixtures/invitations'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { MEMBERS } from '../../../fixtures/members'; -const shareItem = ({ id, fixture }) => { +const shareItem = ({ id, fixture }: { id: string; fixture: string }) => { cy.get(`#${buildShareButtonId(id)}`).click(); cy.get(`#${SHARE_ITEM_CSV_PARSER_BUTTON_ID}`).click(); cy.attachFile( @@ -60,14 +62,25 @@ describe('Share Item From CSV', () => { cy.fixture(fixture).then((data) => { // get content from csv and remove last line: no content - const { data: csvContent } = Papa.parse(data, { header: true }); + const { data: csvContent } = Papa.parse<{ + name: string; + permission: PermissionLevel; + email: string; + }>(data, { header: true }); const csv = csvContent.filter(({ name }) => name); - // david, cedric alredy has an invitation + // david, cedric already has an invitation // garry is a new invitation cy.wait('@postInvitations').then(({ request: { url, body } }) => { expect(url).to.contain(id); - const { invitations } = body; + const { invitations } = body as { + invitations: { + email: string; + permission: PermissionLevel; + name: string; + itemPath: string; + }[]; + }; csv.forEach(({ permission, email }) => { const member = registeredMembers.find( ({ email: mEmail }) => mEmail === email, @@ -87,7 +100,15 @@ describe('Share Item From CSV', () => { // fanny is already a membership cy.wait('@postManyItemMemberships').then(({ request: { url, body } }) => { expect(url).to.contain(id); - const { memberships } = body; + const { memberships } = body as { + memberships: { + name: string; + email: string; + permission: PermissionLevel; + itemPath: string; + memberId: string; + }[]; + }; csv.forEach(({ permission, email }) => { const member = registeredMembers.find( ({ email: mEmail }) => mEmail === email, diff --git a/cypress/e2e/item/view/viewFolder.cy.ts b/cypress/e2e/item/view/viewFolder.cy.ts index 8a8015581..ebf2eb608 100644 --- a/cypress/e2e/item/view/viewFolder.cy.ts +++ b/cypress/e2e/item/view/viewFolder.cy.ts @@ -30,9 +30,11 @@ import { NAVIGATION_LOAD_PAUSE, TABLE_ITEM_RENDER_TIME, } from '../../../support/constants'; +import { ItemForTest } from '../../../support/types'; import { expectFolderViewScreenLayout } from '../../../support/viewUtils'; -const translateBuilder = (key) => i18n.t(key, { ns: namespaces.builder }); +const translateBuilder = (key: string) => + i18n.t(key, { ns: namespaces.builder }); describe('View Folder', () => { describe('Grid', () => { @@ -177,7 +179,10 @@ describe('View Folder', () => { describe('Grid pagination', () => { const sampleItems = generateOwnItems(30); - const checkGridPagination = (items, itemsPerPage) => { + const checkGridPagination = ( + items: ItemForTest[], + itemsPerPage: number, + ) => { const numberPages = Math.ceil(items.length / itemsPerPage); // for each page diff --git a/cypress/e2e/memberProfile.cy.ts b/cypress/e2e/memberProfile.cy.ts index f72a9474f..e95499a1a 100644 --- a/cypress/e2e/memberProfile.cy.ts +++ b/cypress/e2e/memberProfile.cy.ts @@ -39,11 +39,11 @@ describe('Member Profile', () => { cy.get(`#${MEMBER_PROFILE_EMAIL_ID}`).should('contain', email); cy.get(`#${MEMBER_PROFILE_INSCRIPTION_DATE_ID}`).should( 'contain', - formatDate(createdAt, { locale: CURRENT_USER.extra.lang as string }), + formatDate(createdAt, { locale: CURRENT_USER.extra.lang }), ); cy.get(`#${MEMBER_PROFILE_LANGUAGE_SWITCH_ID}`).should( 'contain', - langs[extra.lang as string], + langs[extra.lang as keyof typeof langs], ); cy.get(`#${MEMBER_PROFILE_EMAIL_FREQ_SWITCH_ID}`).should( 'contain', diff --git a/cypress/e2e/memberships/deleteItemMembership.cy.ts b/cypress/e2e/memberships/deleteItemMembership.cy.ts index 6377d39ba..d8c975674 100644 --- a/cypress/e2e/memberships/deleteItemMembership.cy.ts +++ b/cypress/e2e/memberships/deleteItemMembership.cy.ts @@ -6,7 +6,13 @@ import { import { ITEMS_WITH_MEMBERSHIPS } from '../../fixtures/memberships'; import { TABLE_MEMBERSHIP_RENDER_TIME } from '../../support/constants'; -const deleteItemMembership = ({ id, itemId }) => { +const deleteItemMembership = ({ + id, + itemId, +}: { + id: string; + itemId: string; +}) => { cy.get(`#${buildShareButtonId(itemId)}`).click(); cy.wait(TABLE_MEMBERSHIP_RENDER_TIME); cy.get(`#${buildItemMembershipRowDeleteButtonId(id)}`).click(); diff --git a/cypress/e2e/memberships/editItemMembership.cy.ts b/cypress/e2e/memberships/editItemMembership.cy.ts index 814b9fafa..f8bfd9f9f 100644 --- a/cypress/e2e/memberships/editItemMembership.cy.ts +++ b/cypress/e2e/memberships/editItemMembership.cy.ts @@ -10,7 +10,15 @@ import { import { ITEMS_WITH_MEMBERSHIPS } from '../../fixtures/memberships'; import { TABLE_MEMBERSHIP_RENDER_TIME } from '../../support/constants'; -const editItemMembership = ({ itemId, id, permission }) => { +const editItemMembership = ({ + itemId, + id, + permission, +}: { + id: string; + itemId: string; + permission: PermissionLevel; +}) => { cy.get(`#${buildShareButtonId(itemId)}`).click(); cy.wait(TABLE_MEMBERSHIP_RENDER_TIME); diff --git a/cypress/e2e/ws/item.cy.ts b/cypress/e2e/ws/item.cy.ts index 72edf67b5..0608e8b5d 100644 --- a/cypress/e2e/ws/item.cy.ts +++ b/cypress/e2e/ws/item.cy.ts @@ -6,18 +6,22 @@ import { SAMPLE_ITEMS } from '../../fixtures/items'; import { CURRENT_USER } from '../../fixtures/members'; import { WEBSOCKETS_DELAY_TIME } from '../../support/constants'; -// paramaterized before, to be called in each test or in beforeEach -function beforeWs(visitRoute, sampleData, wsClientStub) { +// parameterized before, to be called in each test or in beforeEach +function beforeWs( + visitRoute: string, + sampleData: unknown, + wsClientStub: MockWebSocket, +) { cy.setUpApi(sampleData); cy.visit(visitRoute, { onBeforeLoad: (win) => { - cy.stub(win, 'WebSocket', () => wsClientStub); + cy.stub(win, 'WebSocket').callsFake(() => wsClientStub); }, }); } describe('Websocket interactions', () => { - let client; + let client: MockWebSocket; beforeEach(() => { client = new MockWebSocket(); diff --git a/cypress/fixtures/files.ts b/cypress/fixtures/files.ts index 894a74974..0f723cce7 100644 --- a/cypress/fixtures/files.ts +++ b/cypress/fixtures/files.ts @@ -75,8 +75,8 @@ export const PDF_ITEM_DEFAULT: LocalFileItemForTest = { createFilepath: ICON_FILEPATH, readFilepath: MOCK_PDF_URL, }; - -export const ZIP_DEFAULT = { +export type ZIPInternalItem = { type: InternalItemType.ZIP; filepath: string }; +export const ZIP_DEFAULT: ZIPInternalItem = { type: InternalItemType.ZIP, filepath: 'files/graasp.zip', }; diff --git a/cypress/fixtures/invitations.ts b/cypress/fixtures/invitations.ts index 160bbad7d..e29d20fec 100644 --- a/cypress/fixtures/invitations.ts +++ b/cypress/fixtures/invitations.ts @@ -1,7 +1,7 @@ -import { v4 } from 'uuid'; - import { DiscriminatedItem, Invitation, PermissionLevel } from '@graasp/sdk'; +import { v4 } from 'uuid'; + import { ApiConfig } from '../support/types'; import { DEFAULT_FOLDER_ITEM } from './items'; import { MEMBERS } from './members'; diff --git a/cypress/fixtures/items.ts b/cypress/fixtures/items.ts index fc71048f7..c9ea49a42 100644 --- a/cypress/fixtures/items.ts +++ b/cypress/fixtures/items.ts @@ -225,8 +225,9 @@ export const RECYCLED_ITEM_DATA: RecycledItemData[] = [ ]; export const generateOwnItems = (number: number): ItemForTest[] => { - const id = (i) => `cafebabe-dead-beef-1234-${`${i}`.padStart(12, '0')}`; - const path = (i) => id(i).replace(/-/g, '_'); + const id = (i: number) => + `cafebabe-dead-beef-1234-${`${i}`.padStart(12, '0')}`; + const path = (i: number) => id(i).replace(/-/g, '_'); return Array(number) .fill(null) diff --git a/cypress/fixtures/members.ts b/cypress/fixtures/members.ts index 542f774e1..cf3b38c7c 100644 --- a/cypress/fixtures/members.ts +++ b/cypress/fixtures/members.ts @@ -1,9 +1,9 @@ -import { MemberType } from '@graasp/sdk'; +import { Member, MemberType } from '@graasp/sdk'; import { MemberForTest } from '../support/types'; import { AVATAR_LINK } from './thumbnails/links'; -export const SIGNED_OUT_MEMBER = null; +export const SIGNED_OUT_MEMBER: Member | null = null; export const MEMBERS: Record = { ANNA: { diff --git a/cypress/fixtures/memberships.ts b/cypress/fixtures/memberships.ts index 99d379c6d..c4596da9a 100644 --- a/cypress/fixtures/memberships.ts +++ b/cypress/fixtures/memberships.ts @@ -1,5 +1,3 @@ -import { v4 } from 'uuid'; - import { DiscriminatedItem, ItemMembership, @@ -7,6 +5,8 @@ import { PermissionLevel, } from '@graasp/sdk'; +import { v4 } from 'uuid'; + import { ApiConfig } from '../support/types'; import { DEFAULT_FOLDER_ITEM } from './items'; import { MEMBERS } from './members'; diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts deleted file mode 100644 index 79232d0da..000000000 --- a/cypress/plugins/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable global-require */ - -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ - -module.exports = (on, config) => { - const newConfig = { - ...config, - env: { - 'cypress-react-selector': { - root: '#root', - }, - API_HOST: process.env.REACT_APP_API_HOST, - AUTHENTICATION_HOST: process.env.REACT_APP_AUTHENTICATION_HOST, - }, - }; - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('@cypress/code-coverage/task')(on, newConfig); - return newConfig; -}; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 5a0d15ec2..51c776c78 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,8 +1,8 @@ // eslint-disable-next-line import/no-extraneous-dependencies -import 'cypress-localstorage-commands'; - import { COOKIE_KEYS } from '@graasp/sdk'; +import 'cypress-localstorage-commands'; + import { DEFAULT_ITEM_LAYOUT_MODE } from '../../src/config/constants'; import { ITEM_INFORMATION_BUTTON_ID, diff --git a/cypress/support/commands/item.ts b/cypress/support/commands/item.ts index 609ed19ec..3fee9a487 100644 --- a/cypress/support/commands/item.ts +++ b/cypress/support/commands/item.ts @@ -121,7 +121,7 @@ Cypress.Commands.add( Cypress.Commands.add( 'fillDocumentModal', - ({ name = '', extra = {} }, { confirm = true } = {}) => { + ({ name = '', extra }, { confirm = true } = {}) => { cy.fillBaseItemModal({ name }, { confirm: false }); cy.get(ITEM_FORM_DOCUMENT_TEXT_SELECTOR).type( @@ -137,7 +137,7 @@ Cypress.Commands.add( Cypress.Commands.add( 'fillAppModal', - ({ name = '', extra = {} }, { confirm = true, type = false } = {}) => { + ({ name = '', extra }, { confirm = true, type = false } = {}) => { cy.fillBaseItemModal({ name }, { confirm: false }); cy.get(`#${ITEM_FORM_APP_URL_ID}`).click(); diff --git a/cypress/support/createUtils.ts b/cypress/support/createUtils.ts index 39588a225..ca7f012ab 100644 --- a/cypress/support/createUtils.ts +++ b/cypress/support/createUtils.ts @@ -1,5 +1,6 @@ import { AppItemType, + DiscriminatedItem, DocumentItemType, EmbeddedLinkItemType, ItemType, @@ -17,6 +18,7 @@ import { ZIP_DASHBOARD_UPLOADER_ID, } from '../../src/config/selectors'; import { InternalItemType } from '../../src/config/types'; +import { ZIPInternalItem } from '../fixtures/files'; import { FileItemForTest } from './types'; export const createApp = ( @@ -76,14 +78,9 @@ export const createFile = ( } }; +// todo: question: only used by import zip ?? export const createItem = ( - payload: { - name?: string; - extra?: { [ItemType.LINK]: { url?: string } }; - filepath?: string; - type?: ItemType | InternalItemType; - createFilepath?: string; - }, + payload: DiscriminatedItem | ZIPInternalItem, options?: { confirm?: boolean }, ): void => { cy.get(`#${CREATE_ITEM_BUTTON_ID}`).click(); @@ -101,6 +98,8 @@ export const createItem = ( // drag-drop a file in the uploader cy.attachFile( cy.get(`#${DASHBOARD_UPLOADER_ID} .uppy-Dashboard-input`).first(), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore payload?.createFilepath, { action: 'drag-drop', diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 6ea2af787..9f5f2f859 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -12,11 +12,6 @@ // You can read more here: // https://on.cypress.io/configuration // *********************************************************** -// eslint-disable-next-line import/no-extraneous-dependencies import '@cypress/code-coverage/support'; -// Import commands.js using ES2015 syntax: import './commands'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/cypress/support/index.ts b/cypress/support/index.ts index c36e2f641..3af8af774 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -1,4 +1,9 @@ -import { ItemType, PermissionLevel } from '@graasp/sdk'; +import { + AppItemExtra, + DocumentItemExtra, + ItemType, + PermissionLevel, +} from '@graasp/sdk'; // cypress/support/index.ts declare global { @@ -25,8 +30,12 @@ declare global { openMetadataPanel(): void; - attachFile(selector: Chainable, file: string, options?: object); - attachFiles(selector: Chainable, filenames: string[], options?: object); + attachFile(selector: Chainable, file: string, options?: object): void; + attachFiles( + selector: Chainable, + filenames: string[], + options?: object, + ): void; // TODO visitAndMockWs( @@ -48,9 +57,12 @@ declare global { payload?: { extra?: { [ItemType.LINK]: { url?: string } } }, options?: { confirm?: boolean }, ): void; - fillDocumentModal(payload, options?: { confirm?: boolean }): void; + fillDocumentModal( + payload: { name: string; extra?: DocumentItemExtra }, + options?: { confirm?: boolean }, + ): void; fillAppModal( - payload, + payload: { name: string; extra?: AppItemExtra }, options?: { type?: boolean; confirm?: boolean }, ): void; fillFolderModal( @@ -58,7 +70,7 @@ declare global { arg2?: { confirm?: boolean }, ): void; - dragAndDrop(subject: string, x: number, y: number); + dragAndDrop(subject: string, x: number, y: number): void; // TODO setUpApi(args?: any): any; diff --git a/cypress/support/paths.ts b/cypress/support/paths.ts new file mode 100644 index 000000000..38f8d395b --- /dev/null +++ b/cypress/support/paths.ts @@ -0,0 +1,16 @@ +import { buildSignInPath } from '@graasp/sdk'; + +import { buildItemPath } from '@/config/paths'; + +const GRAASP_PLAYER_HOST = Cypress.env('PLAYER_HOST'); +const GRAASP_BUILDER_HOST = Cypress.env('BUILDER_HOST'); +const GRAASP_ANALYZER_HOST = Cypress.env('ANALYZER_HOST'); + +export const SIGN_IN_PATH = buildSignInPath({ host: Cypress.env('AUTH_HOST') }); + +export const buildGraaspPlayerView = (id: string): string => + `${GRAASP_PLAYER_HOST}/${id}`; +export const buildGraaspBuilderView = (id: string): string => + `${GRAASP_BUILDER_HOST}${buildItemPath(id)}`; +export const buildGraaspAnalyzerLink = (id: string): string => + `${GRAASP_ANALYZER_HOST}/embedded/${id}`; diff --git a/cypress/support/server.ts b/cypress/support/server.ts index d546d3a65..6aabf7d28 100644 --- a/cypress/support/server.ts +++ b/cypress/support/server.ts @@ -1,15 +1,14 @@ -import { StatusCodes } from 'http-status-codes'; -import * as qs from 'qs'; -import { v4 as uuidv4, v4 } from 'uuid'; - import { API_ROUTES } from '@graasp/query-client'; import { App, Category, ChatMention, + DiscriminatedItem, HttpMethod, + Invitation, Item, ItemFavorite, + ItemMembership, ItemPublished, ItemTagType, ItemValidationGroup, @@ -20,7 +19,11 @@ import { } from '@graasp/sdk'; import { FAILURE_MESSAGES } from '@graasp/translations'; -import { SETTINGS, SIGN_IN_PATH } from '../../src/config/constants'; +import { StatusCodes } from 'http-status-codes'; +import * as qs from 'qs'; +import { v4 as uuidv4, v4 } from 'uuid'; + +import { SETTINGS } from '../../src/config/constants'; import { getItemById, isChild, @@ -36,6 +39,7 @@ import { import { buildInvitation } from '../fixtures/invitations'; import { CURRENT_USER, MEMBERS } from '../fixtures/members'; import { AVATAR_LINK, ITEM_THUMBNAIL_LINK } from '../fixtures/thumbnails/links'; +import { SIGN_IN_PATH } from './paths'; import { ItemForTest, MemberForTest } from './types'; import { ID_FORMAT, parseStringToRegExp } from './utils'; @@ -112,7 +116,6 @@ const checkMembership = ({ export const redirectionReply = { headers: { 'content-type': 'application/json' }, statusCode: StatusCodes.OK, - body: null, }; export const mockGetAppListRoute = (apps: App[]): void => { @@ -374,7 +377,10 @@ export const mockGetItems = ({ if (!Array.isArray(itemIds)) { itemIds = [itemIds]; } - const result = { data: {}, errors: [] }; + const result: { + data: { [key: string]: ItemForTest }; + errors: { statusCode: number }[]; + } = { data: {}, errors: [] }; itemIds.forEach((id) => { const item = getItemById(items, id); @@ -389,7 +395,6 @@ export const mockGetItems = ({ if (!haveMembership) { result.errors.push({ statusCode: StatusCodes.UNAUTHORIZED, - body: null, }); } else if (!item) { result.errors.push({ statusCode: StatusCodes.NOT_FOUND }); @@ -610,25 +615,40 @@ export const mockPostManyItemMemberships = ( // return membership or error if membership // for member id already exists - const result = { data: {}, errors: [] }; - - body.memberships.forEach((m) => { - const thisM = itemMemberships?.find( - ({ member }) => m.memberId === member.id, - ); - if (thisM) { - result.errors.push({ - statusCode: StatusCodes.BAD_REQUEST, - message: 'membership already exists', - data: thisM, - }); - } - result.data[m.memberId] = { - permission: m.permission, - member: members?.find(({ id }) => m.memberId === id), - item: items?.find(({ path }) => m.itemPath === path), + const result: { + data: { + [key: string]: { + permission: PermissionLevel; + member: Member; + item: DiscriminatedItem; + }; }; - }); + errors: { statusCode: number; message: string; data: unknown }[]; + } = { data: {}, errors: [] }; + + body.memberships.forEach( + (m: { + itemPath: string; + permission: PermissionLevel; + memberId: string; + }) => { + const thisM = itemMemberships?.find( + ({ member }) => m.memberId === member.id, + ); + if (thisM) { + result.errors.push({ + statusCode: StatusCodes.BAD_REQUEST, + message: 'membership already exists', + data: thisM, + }); + } + result.data[m.memberId] = { + permission: m.permission, + member: members?.find(({ id }) => m.memberId === id), + item: items?.find(({ path }) => m.itemPath === path), + }; + }, + ); return reply(result); }, ).as('postManyItemMemberships'); @@ -671,7 +691,10 @@ export const mockGetMembers = (members: Member[]): void => { memberIds = [memberIds]; } - const result = { + const result: { + data: { [key: string]: Member }; + errors: { statusCode: number; name: string }[]; + } = { data: {}, errors: [], }; @@ -717,7 +740,10 @@ export const mockGetMembersBy = ( } // TODO - const result = { + const result: { + data: { [key: string]: Member }; + errors: unknown[]; + } = { data: {}, errors: [], }; @@ -869,7 +895,7 @@ export const mockPostItemLogin = ( // check query match item login schema const id = url.slice(API_HOST.length).split('/')[2]; const item = getItemById(items, id); - const itemLoginSchema = item.itemLoginSchema[0]; + const { itemLoginSchema } = item; // provide either username or member id if (body.username) { @@ -880,10 +906,10 @@ export const mockPostItemLogin = ( // should have password if required if ( - itemLoginSchema === + itemLoginSchema.type === SETTINGS.ITEM_LOGIN.SIGN_IN_MODE.USERNAME_AND_PASSWORD ) { - expect(body).to.have.keys('password'); + expect(body).to.have.any.keys('password'); } reply({ @@ -951,7 +977,7 @@ export const mockGetItemLogin = (items: ItemForTest[]): void => { const itemId = url.slice(API_HOST.length).split('/')[2]; const item = items.find(({ id }) => itemId === id); reply({ - body: item?.itemLoginSchema?.[0] ?? {}, + body: item?.itemLoginSchema ?? {}, statusCode: StatusCodes.OK, }); }, @@ -1024,7 +1050,12 @@ export const mockGetItemMembershipsForItem = ( const itemId = qs.parse(url.slice(url.indexOf('?') + 1)).itemId as string; const selectedItems = items.filter(({ id }) => itemId.includes(id)); // todo: use reduce - const result = { data: {}, errors: [] }; + const result: { + data: { + [key: string]: ItemMembership[]; + }; + errors: { statusCode: number }[]; + } = { data: {}, errors: [] }; selectedItems.forEach((item) => { const { creator, id, memberships } = item; // build default membership depending on current member @@ -1047,6 +1078,9 @@ export const mockGetItemMembershipsForItem = ( permission: PermissionLevel.Admin, member: creator, item, + id: v4(), + createdAt: new Date(), + updatedAt: new Date(), }, ]; }); @@ -1671,8 +1705,14 @@ export const mockPostInvitations = ( const itemId = url.split('/')[4]; const invitations = items.find(({ id }) => id === itemId)?.invitations; - const result = { data: {}, errors: [] }; - body.invitations.forEach((inv) => { + const result: { + data: { [key: string]: Invitation }; + errors: { statusCode: number; message: string; data: unknown }[]; + } = { + data: {}, + errors: [], + }; + body.invitations.forEach((inv: Parameters[0]) => { const thisInv = invitations?.find(({ email }) => email === inv.email); if (thisInv) { result.errors.push({ diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index e8bea7f90..0b9118d01 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -36,11 +36,11 @@ export const parseStringToRegExp = ( export const EMAIL_FORMAT = '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'; -const validateCsvData = (data, numMessages) => { +const validateCsvData = (data: unknown[], numMessages: number) => { expect(data).to.have.length(numMessages); }; -const validateCsvHeaders = (headers) => { +const validateCsvHeaders = (headers: string[]) => { const expectedHeader = EXPORT_CSV_HEADERS.map((h) => h.label); expect(headers).to.deep.equal(expectedHeader); }; diff --git a/cypress/support/viewUtils.ts b/cypress/support/viewUtils.ts index ff739415a..8df7fa19c 100644 --- a/cypress/support/viewUtils.ts +++ b/cypress/support/viewUtils.ts @@ -40,15 +40,15 @@ const expectPanelLayout = ({ item }: { item: ItemForTest }) => { const panel = cy.get(`#${ITEM_PANEL_ID}`); panel.get(`#${ITEM_PANEL_NAME_ID}`).contains(name); - const creatorName = getMemberById(Object.values(MEMBERS), creator.id).name; + const creatorName = getMemberById(Object.values(MEMBERS), creator?.id).name; panel.get(`#${ITEM_PANEL_TABLE_ID}`).should('exist').contains(creatorName); if (item.type === ItemType.LOCAL_FILE || item.type === ItemType.S3_FILE) { - const { mimetype, size } = + const { mimetype = 'invalid-mimetype', size = 'invalid-size' } = item.type === ItemType.LOCAL_FILE - ? getFileExtra(item.extra) - : getS3FileExtra(item.extra); + ? getFileExtra(item.extra) || {} + : getS3FileExtra(item.extra) || {}; panel.get(`#${ITEM_PANEL_TABLE_ID}`).contains(mimetype); panel.get(`#${ITEM_PANEL_TABLE_ID}`).contains(size); @@ -123,7 +123,7 @@ export const expectLinkViewScreenLayout = ({ currentMember?: MemberForTest; }): void => { const { id, description, settings } = item; - const { url, html } = getEmbeddedLinkExtra(item.extra); + const { url, html } = getEmbeddedLinkExtra(item.extra) || {}; // embedded element if (html) { diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 156a760b6..6b07d54d8 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -3,9 +3,11 @@ "compilerOptions": { "target": "es5", "lib": ["es5", "dom", "ES2021.String"], - "types": ["cypress", "node"], - "strict": false, + "types": ["cypress", "node", "vite/client"], + "strictNullChecks": false, + "strict": true, "sourceMap": false }, - "include": ["**/*.ts", "cypress.d.ts"] + "include": ["**/*.ts", "cypress.d.ts"], + "exclude": ["coverage", ".nyc_output"] } diff --git a/index.html b/index.html new file mode 100644 index 000000000..2b75bc50d --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + Graasp Builder + + +
+ + + diff --git a/package.json b/package.json index ce49c1797..f8bdbdc51 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,22 @@ "Basile Spaenlehauer", "Alexandre Chau" ], + "engines": { + "node": ">=16" + }, "dependencies": { "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", "@graasp/chatbox": "2.0.0", - "@graasp/query-client": "1.3.1", - "@graasp/sdk": "1.1.3", - "@graasp/translations": "1.17.0", - "@graasp/ui": "3.2.6", + "@graasp/query-client": "1.3.2", + "@graasp/sdk": "1.2.0", + "@graasp/translations": "1.18.1", + "@graasp/ui": "3.3.0", "@mui/icons-material": "5.14.3", - "@mui/lab": "5.0.0-alpha.137", - "@mui/material": "5.14.3", - "@sentry/react": "7.60.0", - "@sentry/tracing": "7.60.0", + "@mui/lab": "5.0.0-alpha.138", + "@mui/material": "5.14.4", + "@sentry/react": "7.61.1", + "@sentry/tracing": "7.61.1", "@uppy/core": "3.3.1", "@uppy/dashboard": "3.4.2", "@uppy/drag-drop": "3.0.2", @@ -34,7 +37,7 @@ "ag-grid-community": "29.3.3", "ag-grid-react": "29.3.3", "date-fns": "2.30.0", - "filesize": "10.0.7", + "filesize": "10.0.8", "http-status-codes": "2.2.0", "immutable": "4.3.1", "katex": "0.16.7", @@ -42,44 +45,45 @@ "lodash.partition": "4.6.0", "lodash.truncate": "4.4.2", "papaparse": "5.4.1", - "prop-types": "15.8.1", "qs": "6.11.2", - "react": "^17.0.2", + "react": "18.2.0", "react-beautiful-dnd": "13.1.1", "react-csv": "2.2.2", - "react-dom": "^17.0.2", + "react-dom": "18.2.0", "react-ga4": "2.1.0", "react-i18next": "11.18.6", "react-image-crop": "9.1.1", "react-query": "3.39.3", "react-quill": "2.0.0", - "react-router": "6.13.0", - "react-router-dom": "6.13.0", - "react-scripts": "5.0.1", + "react-router": "6.14.2", + "react-router-dom": "6.14.2", "react-toastify": "9.1.3", + "stylis-plugin-rtl": "2.1.1", "uuid": "9.0.0", - "validator": "13.9.0" + "validator": "13.11.0" }, "scripts": { - "hooks:uninstall": "husky uninstall", - "hooks:install": "husky install", - "start": "env-cmd -f ./.env.local react-scripts start", - "start:test": "env-cmd -f ./.env.test react-scripts start", - "start:prod": "env-cmd -f ./.env.production react-scripts start", - "start:ci": "react-scripts -r @cypress/instrument-cra start", - "build": "react-scripts build", - "build:local": "env-cmd -f ./.env.local react-scripts build", - "dist": "env-cmd -f ./.env.production react-scripts build", - "dist:dev": "env-cmd -f ./.env.development react-scripts build", - "eject": "react-scripts eject", - "lint": "eslint --resolve-plugins-relative-to .", - "type-check": "tsc --noEmit && tsc --noEmit -p cypress/tsconfig.json", - "check": "yarn prettier:check && yarn lint && yarn type-check", + "dev": "vite", + "start": "yarn dev", + "start:test": "vite --mode test", + "build": "vite build", + "build:dev": "vite build --mode development", + "build:test": "vite build --mode test", + "preview": "vite preview", + "preview:dev": "vite preview --mode development", + "preview:test": "vite preview --mode test", + "lint": "eslint .", + "pre-commit": "yarn prettier:check && yarn lint && yarn type-check", "prettier:check": "prettier --check {src,cypress}/**/*.{js,ts,tsx,json}", "prettier:write": "prettier --write {src,cypress}/**/*.{js,ts,tsx,json}", - "cypress:open": "env-cmd -f ./.env.local cypress open", - "cypress": "concurrently -k -s first \"yarn start:test\" \"wait-on http://localhost:3111 && yarn cypress:run\"", - "cypress:run": "env-cmd -f ./.env.test cypress run", + "type-check": "tsc --noEmit && tsc --noEmit -p cypress/tsconfig.json", + "check": "yarn prettier:check && yarn lint && yarn type-check", + "hooks:uninstall": "husky uninstall", + "hooks:install": "husky install", + "cypress:open": "env-cmd -f ./.env.development cypress open --browser chrome", + "cypress": "yarn test", + "test": "yarn build:test && concurrently -k -s first \"yarn preview:test\" \"yarn cypress:run\"", + "cypress:run": "env-cmd -f ./.env.test cypress run --headless --browser chrome", "postinstall": "husky install" }, "browserslist": { @@ -95,10 +99,9 @@ ] }, "devDependencies": { - "@commitlint/cli": "17.6.7", - "@commitlint/config-conventional": "17.6.7", - "@cypress/code-coverage": "3.10.9", - "@cypress/instrument-cra": "1.4.0", + "@commitlint/cli": "17.7.0", + "@commitlint/config-conventional": "17.7.0", + "@cypress/code-coverage": "3.11.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", @@ -106,44 +109,37 @@ "@types/jest": "29.5.3", "@types/lodash.partition": "4.6.7", "@types/lodash.truncate": "4.4.7", - "@types/node": "18.16.19", + "@types/node": "18.17.4", "@types/papaparse": "5.3.7", "@types/qs": "6.9.7", - "@types/react": "^17.0.58", + "@types/react": "18.2.19", "@types/react-csv": "1.1.3", - "@types/react-dom": "^17.0.20", + "@types/react-dom": "18.2.7", "@types/uuid": "9.0.2", - "@types/validator": "13.7.17", - "@typescript-eslint/eslint-plugin": "5.62.0", - "@typescript-eslint/parser": "5.62.0", - "concurrently": "8.1.0", - "cypress": "12.13.0", + "@types/validator": "13.11.1", + "@typescript-eslint/eslint-plugin": "6.3.0", + "@typescript-eslint/parser": "6.3.0", + "@vitejs/plugin-react": "4.0.4", + "concurrently": "8.2.0", + "cypress": "12.17.3", "cypress-localstorage-commands": "2.2.3", "env-cmd": "10.1.0", - "eslint": "^8.45.0", + "eslint": "^8.46.0", "eslint-config-airbnb": "19.0.4", - "eslint-config-prettier": "8.8.0", - "eslint-config-react-app": "^7.0.1", + "eslint-config-prettier": "9.0.0", "eslint-import-resolver-typescript": "3.5.5", - "eslint-plugin-import": "2.27.5", + "eslint-plugin-import": "2.28.0", "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-react": "7.32.2", + "eslint-plugin-react": "7.33.1", + "eslint-plugin-react-hooks": "4.6.0", "husky": "8.0.3", - "istanbul-lib-coverage": "3.2.0", "nyc": "15.1.0", "prettier": "3.0.1", - "react-app-rewired": "2.2.1", + "rollup-plugin-visualizer": "5.9.2", "typescript": "5.1.6", - "wait-on": "7.0.1", - "webpack-bundle-analyzer": "4.9.0" - }, - "resolutions": { - "immer": "10.0.2", - "node-forge": "1.3.1", - "nth-check": "2.1.1", - "react-error-overlay": "6.0.11", - "@types/react": "17.0.62", - "@svgr/webpack": "8.0.1" + "vite": "4.4.9", + "vite-plugin-checker": "0.6.1", + "vite-plugin-istanbul": "5.0.0" }, "packageManager": "yarn@3.6.1" } diff --git a/public/index.html b/public/index.html deleted file mode 100644 index dbd01842a..000000000 --- a/public/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - Graasp Builder - - - - -
- - diff --git a/scripts/analyze.js b/scripts/analyze.js deleted file mode 100644 index 20aa54e89..000000000 --- a/scripts/analyze.js +++ /dev/null @@ -1,37 +0,0 @@ -process.env.NODE_ENV = 'production'; - -const webpack = require('webpack'); -const BundleAnalyzerPlugin = - require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -const webpackConfigProd = require('react-scripts/config/webpack.config')( - 'production', -); - -// this one is optional, just for better feedback on build -const chalk = require('chalk'); -const ProgressBarPlugin = require('progress-bar-webpack-plugin'); -const green = (text) => { - return chalk.green.bold(text); -}; - -// pushing BundleAnalyzerPlugin to plugins array -webpackConfigProd.plugins.push(new BundleAnalyzerPlugin()); - -// optional - pushing progress-bar plugin for better feedback; -// it can and will work without progress-bar, -// but during build time you will not see any messages for 10-60 seconds (depends on the size of the project) -// and decide that compilation is kind of hang up on you; progress bar shows nice progression of webpack compilation -webpackConfigProd.plugins.push( - new ProgressBarPlugin({ - format: `${green('analyzing...')} ${green('[:bar]')}${green( - '[:percent]', - )}${green('[:elapsed seconds]')} - :msg`, - }), -); - -// actually running compilation and waiting for plugin to start explorer -webpack(webpackConfigProd, (err, stats) => { - if (err || stats.hasErrors()) { - console.error(err); - } -}); diff --git a/src/components/App.tsx b/src/components/App.tsx index 7fc0e6da9..c82175b4e 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -4,7 +4,9 @@ import { Route, Routes } from 'react-router-dom'; import { saveUrlForRedirection } from '@graasp/sdk'; import { CustomInitialLoader, withAuthorization } from '@graasp/ui'; -import { DOMAIN, SIGN_IN_PATH } from '../config/constants'; +import { DOMAIN } from '@/config/env'; +import { SIGN_IN_PATH } from '@/config/externalPaths'; + import { FAVORITE_ITEMS_PATH, HOME_PATH, diff --git a/src/components/RecycleBinScreen.tsx b/src/components/RecycleBinScreen.tsx index ab1ee7546..a0e1f1327 100644 --- a/src/components/RecycleBinScreen.tsx +++ b/src/components/RecycleBinScreen.tsx @@ -1,11 +1,11 @@ -import { List } from 'immutable'; - import { Box } from '@mui/material'; import { ItemRecord } from '@graasp/sdk/frontend'; import { BUILDER } from '@graasp/translations'; import { Loader } from '@graasp/ui'; +import { List } from 'immutable'; + import { useBuilderTranslation } from '../config/i18n'; import { hooks } from '../config/queryClient'; import { diff --git a/src/components/Root.tsx b/src/components/Root.tsx index 0410e1907..0448a8a97 100644 --- a/src/components/Root.tsx +++ b/src/components/Root.tsx @@ -1,18 +1,17 @@ -import '@uppy/core/dist/style.css'; -import 'ag-grid-community/styles/ag-grid.min.css'; -import 'ag-grid-community/styles/ag-theme-material.min.css'; - -import { CssBaseline } from '@mui/material'; -import { ThemeProvider } from '@mui/material/styles'; - import { I18nextProvider } from 'react-i18next'; import { BrowserRouter as Router } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import { CssBaseline } from '@mui/material'; +import { ThemeProvider } from '@mui/material/styles'; + import { theme as GraaspTheme } from '@graasp/ui'; -import { ENV, NODE_ENV } from '../config/constants'; +import '@uppy/core/dist/style.css'; +import 'ag-grid-community/styles/ag-grid.min.css'; +import 'ag-grid-community/styles/ag-theme-material.min.css'; + import i18nConfig from '../config/i18n'; import { QueryClientProvider, @@ -38,7 +37,7 @@ const Root = (): JSX.Element => ( - {NODE_ENV === ENV.DEVELOPMENT && } + {import.meta.env.DEV && } ); diff --git a/src/components/SharedItems.tsx b/src/components/SharedItems.tsx index 084072306..120fda380 100644 --- a/src/components/SharedItems.tsx +++ b/src/components/SharedItems.tsx @@ -1,10 +1,10 @@ -import { List } from 'immutable'; - import Box from '@mui/material/Box'; import { BUILDER } from '@graasp/translations'; import { Loader } from '@graasp/ui'; +import { List } from 'immutable'; + import { useBuilderTranslation } from '../config/i18n'; import { hooks } from '../config/queryClient'; import { diff --git a/src/components/common/CancelButton.tsx b/src/components/common/CancelButton.tsx index 7df924f4e..2798fee88 100644 --- a/src/components/common/CancelButton.tsx +++ b/src/components/common/CancelButton.tsx @@ -1,7 +1,7 @@ -import { ButtonProps } from '@mui/material/Button'; - import { MouseEventHandler } from 'react'; +import { ButtonProps } from '@mui/material/Button'; + import { COMMON } from '@graasp/translations'; import { Button } from '@graasp/ui'; diff --git a/src/components/common/CollapseButton.tsx b/src/components/common/CollapseButton.tsx index f5dd901e8..034698553 100644 --- a/src/components/common/CollapseButton.tsx +++ b/src/components/common/CollapseButton.tsx @@ -1,3 +1,5 @@ +import { useEffect, useState } from 'react'; + import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import IconButton from '@mui/material/IconButton'; @@ -5,8 +7,6 @@ import ListItemIcon from '@mui/material/ListItemIcon'; import MenuItem from '@mui/material/MenuItem'; import Tooltip from '@mui/material/Tooltip'; -import { useEffect, useState } from 'react'; - import { Item } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { ActionButton, ActionButtonVariant } from '@graasp/ui'; diff --git a/src/components/common/CookiesBanner.tsx b/src/components/common/CookiesBanner.tsx index a7cec74a4..1d66db301 100644 --- a/src/components/common/CookiesBanner.tsx +++ b/src/components/common/CookiesBanner.tsx @@ -2,7 +2,8 @@ import { COOKIE_KEYS } from '@graasp/sdk'; import { COMMON } from '@graasp/translations'; import { CookiesBanner } from '@graasp/ui'; -import { DOMAIN } from '../../config/constants'; +import { DOMAIN } from '@/config/env'; + import { useCommonTranslation } from '../../config/i18n'; const Component = (): JSX.Element => { diff --git a/src/components/common/CropModal.tsx b/src/components/common/CropModal.tsx index 787fabc76..2fcdbdf11 100644 --- a/src/components/common/CropModal.tsx +++ b/src/components/common/CropModal.tsx @@ -1,13 +1,13 @@ +import { useRef, useState } from 'react'; +import ReactCrop, { Crop, PixelCrop } from 'react-image-crop'; +import 'react-image-crop/dist/ReactCrop.css'; + import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; -import { useRef, useState } from 'react'; -import ReactCrop, { Crop, PixelCrop } from 'react-image-crop'; -import 'react-image-crop/dist/ReactCrop.css'; - import { BUILDER } from '@graasp/translations'; import { Button } from '@graasp/ui'; diff --git a/src/components/common/DeleteButton.tsx b/src/components/common/DeleteButton.tsx index 21c8d9f74..bb7102833 100644 --- a/src/components/common/DeleteButton.tsx +++ b/src/components/common/DeleteButton.tsx @@ -1,7 +1,7 @@ -import { IconButtonProps } from '@mui/material/IconButton'; - import { useState } from 'react'; +import { IconButtonProps } from '@mui/material/IconButton'; + import { BUILDER } from '@graasp/translations'; import { ActionButtonVariant, diff --git a/src/components/common/FavoriteButton.tsx b/src/components/common/FavoriteButton.tsx index 4fd5caeda..9fb302d23 100644 --- a/src/components/common/FavoriteButton.tsx +++ b/src/components/common/FavoriteButton.tsx @@ -1,5 +1,3 @@ -import { List } from 'immutable'; - import { IconButtonProps } from '@mui/material/IconButton'; import { ItemFavoriteRecord, ItemRecord } from '@graasp/sdk/frontend'; @@ -9,6 +7,8 @@ import { FavoriteButton as GraaspFavoriteButton, } from '@graasp/ui'; +import { List } from 'immutable'; + import { useBuilderTranslation } from '../../config/i18n'; import { hooks, mutations } from '../../config/queryClient'; import { FAVORITE_ITEM_BUTTON_CLASS } from '../../config/selectors'; diff --git a/src/components/common/MoveButton.tsx b/src/components/common/MoveButton.tsx index e0f7f3a51..f807bf0fb 100644 --- a/src/components/common/MoveButton.tsx +++ b/src/components/common/MoveButton.tsx @@ -1,7 +1,7 @@ -import { IconButtonProps } from '@mui/material/IconButton'; - import { useContext } from 'react'; +import { IconButtonProps } from '@mui/material/IconButton'; + import { BUILDER } from '@graasp/translations'; import { ActionButton, diff --git a/src/components/common/PlayerViewButton.tsx b/src/components/common/PlayerViewButton.tsx index 7ed1ec312..fd1f44a10 100644 --- a/src/components/common/PlayerViewButton.tsx +++ b/src/components/common/PlayerViewButton.tsx @@ -5,9 +5,10 @@ import { redirect } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { PlayIcon } from '@graasp/ui'; -import { ITEM_HEADER_ICON_HEIGHT } from '../../config/constants'; -import { useBuilderTranslation } from '../../config/i18n'; -import { buildGraaspPlayerView } from '../../config/paths'; +import { ITEM_HEADER_ICON_HEIGHT } from '@/config/constants'; +import { buildGraaspPlayerView } from '@/config/externalPaths'; +import { useBuilderTranslation } from '@/config/i18n'; + import { buildPlayerButtonId, buildPlayerTabName, diff --git a/src/components/common/UserSwitchWrapper.tsx b/src/components/common/UserSwitchWrapper.tsx index 5c1626323..32c4cbf04 100644 --- a/src/components/common/UserSwitchWrapper.tsx +++ b/src/components/common/UserSwitchWrapper.tsx @@ -2,17 +2,18 @@ import { MemberRecord } from '@graasp/sdk/frontend'; import { BUILDER } from '@graasp/translations'; import { UserSwitchWrapper as GraaspUserSwitch } from '@graasp/ui'; -import { SIGN_IN_PATH } from '../../config/constants'; -import { useBuilderTranslation } from '../../config/i18n'; -import { MEMBER_PROFILE_PATH } from '../../config/paths'; -import { mutations } from '../../config/queryClient'; +import { SIGN_IN_PATH } from '@/config/externalPaths'; +import { useBuilderTranslation } from '@/config/i18n'; +import { MEMBER_PROFILE_PATH } from '@/config/paths'; +import { mutations } from '@/config/queryClient'; import { HEADER_MEMBER_MENU_BUTTON_ID, HEADER_MEMBER_MENU_SEE_PROFILE_BUTTON_ID, HEADER_MEMBER_MENU_SIGN_IN_BUTTON_ID, HEADER_MEMBER_MENU_SIGN_OUT_BUTTON_ID, buildMemberMenuItemId, -} from '../../config/selectors'; +} from '@/config/selectors'; + import { useCurrentUserContext } from '../context/CurrentUserContext'; import MemberAvatar from './MemberAvatar'; diff --git a/src/components/context/CopyItemModalContext.tsx b/src/components/context/CopyItemModalContext.tsx index 36a1fc214..84f8d7fc0 100644 --- a/src/components/context/CopyItemModalContext.tsx +++ b/src/components/context/CopyItemModalContext.tsx @@ -1,9 +1,9 @@ -import { validate } from 'uuid'; - import { createContext, useContext, useMemo, useState } from 'react'; import { BUILDER } from '@graasp/translations'; +import { validate } from 'uuid'; + import { useBuilderTranslation } from '../../config/i18n'; import { mutations } from '../../config/queryClient'; import TreeModal, { TreeModalProps } from '../main/TreeModal'; diff --git a/src/components/context/EditItemModalContext.tsx b/src/components/context/EditItemModalContext.tsx index 8af60c396..57aea9c9a 100644 --- a/src/components/context/EditItemModalContext.tsx +++ b/src/components/context/EditItemModalContext.tsx @@ -1,12 +1,12 @@ +import { createContext, useMemo, useState } from 'react'; +import { toast } from 'react-toastify'; + import { TextField } from '@mui/material'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; -import { createContext, useMemo, useState } from 'react'; -import { toast } from 'react-toastify'; - import { routines } from '@graasp/query-client'; import { DiscriminatedItem, @@ -95,7 +95,7 @@ const EditItemModalProvider = ({ children }: Props): JSX.Element => { ...updatedProperties, }) ) { - toast.error(translateBuilder(BUILDER.EDIT_ITEM_ERROR_MESSAGE)); + toast.error(translateBuilder(BUILDER.EDIT_ITEM_ERROR_MESSAGE)); return; } diff --git a/src/components/context/FlagItemModalContext.tsx b/src/components/context/FlagItemModalContext.tsx index 3a22bb854..9e2ba27e4 100644 --- a/src/components/context/FlagItemModalContext.tsx +++ b/src/components/context/FlagItemModalContext.tsx @@ -1,11 +1,11 @@ -import { List } from 'immutable'; - import { createContext, useMemo, useState } from 'react'; import { routines } from '@graasp/query-client'; import { FlagType } from '@graasp/sdk'; import { ItemFlagDialog } from '@graasp/ui'; +import { List } from 'immutable'; + import { useBuilderTranslation } from '../../config/i18n'; import notifier from '../../config/notifier'; import { mutations } from '../../config/queryClient'; diff --git a/src/components/context/MoveItemModalContext.tsx b/src/components/context/MoveItemModalContext.tsx index cc5a4965d..c6afc104b 100644 --- a/src/components/context/MoveItemModalContext.tsx +++ b/src/components/context/MoveItemModalContext.tsx @@ -1,9 +1,9 @@ -import { validate } from 'uuid'; - import { createContext, useMemo, useState } from 'react'; import { BUILDER } from '@graasp/translations'; +import { validate } from 'uuid'; + import { useBuilderTranslation } from '../../config/i18n'; import { mutations } from '../../config/queryClient'; import { TreePreventSelection } from '../../enums'; diff --git a/src/components/file/FileDashboardUploader.js b/src/components/file/FileDashboardUploader.tsx similarity index 69% rename from src/components/file/FileDashboardUploader.js rename to src/components/file/FileDashboardUploader.tsx index 4318ae36e..5d1925ddd 100644 --- a/src/components/file/FileDashboardUploader.js +++ b/src/components/file/FileDashboardUploader.tsx @@ -1,13 +1,15 @@ -import '@uppy/dashboard/dist/style.css'; -import { Dashboard } from '@uppy/react'; - -import Typography from '@mui/material/Typography'; - import { useContext } from 'react'; +import { Typography } from '@mui/material'; + import { MAX_FILE_SIZE } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; +import '@uppy/dashboard/dist/style.css'; +import DashboardLocale from '@uppy/dashboard/types/generatedLocale'; +import { Dashboard } from '@uppy/react'; +import { StatusBarLocale } from '@uppy/status-bar'; + import { FILE_UPLOAD_MAX_FILES } from '../../config/constants'; import { useBuilderTranslation } from '../../config/i18n'; import { DASHBOARD_UPLOADER_ID } from '../../config/selectors'; @@ -15,9 +17,11 @@ import { humanFileSize } from '../../utils/uppy'; import ErrorAlert from '../common/ErrorAlert'; import { UppyContext } from './UppyContext'; -const FileDashboardUploader = () => { +const FileDashboardUploader = (): JSX.Element => { const { uppy } = useContext(UppyContext); const { t: translateBuilder, i18n } = useBuilderTranslation(); + const uppyLocales = i18n.options.resources?.[i18n.language] + ?.uppy as (DashboardLocale & StatusBarLocale)['strings']; if (!uppy) { return ; @@ -28,10 +32,10 @@ const FileDashboardUploader = () => { {translateBuilder(BUILDER.UPLOAD_FILE_TITLE)} - + {translateBuilder(BUILDER.UPLOAD_FILE_INFORMATIONS)} - + {translateBuilder(BUILDER.UPLOAD_FILE_LIMITATIONS_TEXT, { maxFiles: FILE_UPLOAD_MAX_FILES, maxSize: humanFileSize(MAX_FILE_SIZE), @@ -43,9 +47,13 @@ const FileDashboardUploader = () => { height={200} width="100%" proudlyDisplayPoweredByUppy={false} - locale={{ - strings: i18n.options.resources[i18n.language].uppy, - }} + locale={ + uppyLocales + ? { + strings: uppyLocales, + } + : undefined + } /> diff --git a/src/components/file/FileUploader.tsx b/src/components/file/FileUploader.tsx index 490372e31..0cc24a0cd 100644 --- a/src/components/file/FileUploader.tsx +++ b/src/components/file/FileUploader.tsx @@ -1,13 +1,13 @@ -import '@uppy/drag-drop/dist/style.css'; -import { DragDrop } from '@uppy/react'; +import { DragEventHandler, useContext, useEffect, useState } from 'react'; import { Box, styled } from '@mui/material'; -import { DragEventHandler, useContext, useEffect, useState } from 'react'; - import { MAX_FILE_SIZE } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; +import '@uppy/drag-drop/dist/style.css'; +import { DragDrop } from '@uppy/react'; + import { DRAWER_WIDTH, FILE_UPLOAD_MAX_FILES, diff --git a/src/components/file/StatusBar.js b/src/components/file/StatusBar.tsx similarity index 82% rename from src/components/file/StatusBar.js rename to src/components/file/StatusBar.tsx index cebec8246..0fb6b2207 100644 --- a/src/components/file/StatusBar.js +++ b/src/components/file/StatusBar.tsx @@ -1,7 +1,3 @@ -import { StatusBar as UppyStatusBar } from '@uppy/react'; -import '@uppy/status-bar/dist/style.css'; -import PropTypes from 'prop-types'; - import CloseIcon from '@mui/icons-material/Close'; import { styled } from '@mui/material'; import IconButton from '@mui/material/IconButton'; @@ -9,6 +5,10 @@ import Snackbar from '@mui/material/Snackbar'; import SnackbarContent from '@mui/material/SnackbarContent'; import { grey } from '@mui/material/colors'; +import Uppy from '@uppy/core'; +import { StatusBar as UppyStatusBar } from '@uppy/react'; +import '@uppy/status-bar/dist/style.css'; + const StyledSnackbarContent = styled(SnackbarContent)(() => ({ '&.MuiSnackbarContent-root': { background: 'white', @@ -21,7 +21,17 @@ const StyledSnackbarContent = styled(SnackbarContent)(() => ({ }, })); -const StatusBar = ({ handleClose, open, uppy }) => { +type Props = { + handleClose: () => void; + open?: boolean; + uppy?: Uppy; +}; + +const StatusBar = ({ + handleClose, + open = false, + uppy, +}: Props): JSX.Element | null => { if (!uppy) { return null; } @@ -58,15 +68,4 @@ const StatusBar = ({ handleClose, open, uppy }) => { ); }; -StatusBar.propTypes = { - handleClose: PropTypes.func.isRequired, - open: PropTypes.bool, - uppy: PropTypes.shape({}), -}; - -StatusBar.defaultProps = { - open: false, - uppy: null, -}; - export default StatusBar; diff --git a/src/components/file/UppyContext.js b/src/components/file/UppyContext.tsx similarity index 56% rename from src/components/file/UppyContext.js rename to src/components/file/UppyContext.tsx index 67e666675..4d19255c9 100644 --- a/src/components/file/UppyContext.js +++ b/src/components/file/UppyContext.tsx @@ -1,36 +1,52 @@ -import PropTypes from 'prop-types'; +import { + ReactElement, + createContext, + useEffect, + useMemo, + useState, +} from 'react'; -import { createContext, useEffect, useMemo, useState } from 'react'; +import Uppy, { ErrorCallback, UploadCompleteCallback } from '@uppy/core'; import { mutations } from '../../config/queryClient'; import { configureFileUppy } from '../../utils/uppy'; import StatusBar from './StatusBar'; -const UppyContext = createContext(); +type UppyContextType = { + uppy?: Uppy; +}; -const UppyContextProvider = ({ enable, itemId, children }) => { - const [uppy, setUppy] = useState(null); +const UppyContext = createContext({ uppy: undefined }); + +const UppyContextProvider = ({ + enable = false, + itemId, + children, +}: { + enable?: boolean; + itemId?: string; + children: ReactElement | (ReactElement | undefined)[]; +}): JSX.Element => { + const [uppy, setUppy] = useState(); const [openStatusBar, setOpenStatusBar] = useState(false); const { mutate: onFileUploadComplete } = mutations.useUploadFiles(); - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - + const handleClose = () => { setOpenStatusBar(false); }; - const onComplete = (result) => { + const onComplete: UploadCompleteCallback<{ [key: string]: unknown }> = ( + result, + ) => { if (!result?.failed.length) { - const data = result.successful[0].response.body; + const data = result.successful[0].response?.body; onFileUploadComplete({ id: itemId, data }); } return false; }; - const onError = (error) => { + const onError: ErrorCallback = (error) => { onFileUploadComplete({ id: itemId, error }); }; @@ -41,6 +57,9 @@ const UppyContextProvider = ({ enable, itemId, children }) => { useEffect(() => { if (enable) { setUppy( + // todo: remove + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore configureFileUppy({ itemId, onComplete, @@ -62,16 +81,4 @@ const UppyContextProvider = ({ enable, itemId, children }) => { ); }; -UppyContextProvider.propTypes = { - children: PropTypes.node, - enable: PropTypes.bool, - itemId: PropTypes.string, -}; - -UppyContextProvider.defaultProps = { - children: null, - enable: false, - itemId: undefined, -}; - export { UppyContext, UppyContextProvider }; diff --git a/src/components/item/ItemContent.tsx b/src/components/item/ItemContent.tsx index 891bd44a8..95f9304d6 100644 --- a/src/components/item/ItemContent.tsx +++ b/src/components/item/ItemContent.tsx @@ -1,9 +1,7 @@ -import { List } from 'immutable'; +import { useState } from 'react'; import { Button, Container, styled } from '@mui/material'; -import { useState } from 'react'; - import { Api } from '@graasp/query-client'; import { Context, @@ -39,12 +37,14 @@ import { SaveButton, } from '@graasp/ui'; +import { List } from 'immutable'; + +import { API_HOST, H5P_INTEGRATION_URL } from '@/config/env'; + import { - API_HOST, DEFAULT_LINK_SHOW_BUTTON, DEFAULT_LINK_SHOW_IFRAME, GRAASP_ASSETS_URL, - H5P_INTEGRATION_URL, ITEM_DEFAULT_HEIGHT, } from '../../config/constants'; import { useCommonTranslation } from '../../config/i18n'; diff --git a/src/components/item/ItemSearch.tsx b/src/components/item/ItemSearch.tsx index 50ab88da2..6325e1a76 100644 --- a/src/components/item/ItemSearch.tsx +++ b/src/components/item/ItemSearch.tsx @@ -1,13 +1,13 @@ -import { List } from 'immutable'; +import { ChangeEvent, useState } from 'react'; import Typography from '@mui/material/Typography'; -import { ChangeEvent, useState } from 'react'; - import { ItemRecord } from '@graasp/sdk/frontend'; import { BUILDER } from '@graasp/translations'; import { SearchInput } from '@graasp/ui'; +import { List } from 'immutable'; + import { useBuilderTranslation } from '../../config/i18n'; import { ITEMS_GRID_NO_SEARCH_RESULT_ID, diff --git a/src/components/item/form/AppForm.tsx b/src/components/item/form/AppForm.tsx index 5a475d64a..9a30a5320 100644 --- a/src/components/item/form/AppForm.tsx +++ b/src/components/item/form/AppForm.tsx @@ -1,10 +1,10 @@ +import { HTMLAttributes, useState } from 'react'; + import { TextField } from '@mui/material'; import Autocomplete from '@mui/material/Autocomplete'; import Skeleton from '@mui/material/Skeleton'; import Typography from '@mui/material/Typography'; -import { HTMLAttributes, useState } from 'react'; - import { AppItemType, DiscriminatedItem, Item, getAppExtra } from '@graasp/sdk'; import { AppItemTypeRecord, AppRecord } from '@graasp/sdk/frontend'; import { BUILDER } from '@graasp/translations'; diff --git a/src/components/item/form/BaseItemForm.tsx b/src/components/item/form/BaseItemForm.tsx index 9f7719729..42806d3c8 100644 --- a/src/components/item/form/BaseItemForm.tsx +++ b/src/components/item/form/BaseItemForm.tsx @@ -1,7 +1,7 @@ -import { TextField } from '@mui/material'; - import { ChangeEvent } from 'react'; +import { TextField } from '@mui/material'; + import { DiscriminatedItem } from '@graasp/sdk'; import { ItemRecord } from '@graasp/sdk/frontend'; import { BUILDER } from '@graasp/translations'; diff --git a/src/components/item/form/DocumentForm.tsx b/src/components/item/form/DocumentForm.tsx index e7e9b0cc8..497f434ed 100644 --- a/src/components/item/form/DocumentForm.tsx +++ b/src/components/item/form/DocumentForm.tsx @@ -1,9 +1,9 @@ +import { useEffect, useState } from 'react'; + import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import { useEffect, useState } from 'react'; - import { DocumentItemExtraFlavor, DocumentItemExtraProperties, diff --git a/src/components/item/form/EtherpadForm.tsx b/src/components/item/form/EtherpadForm.tsx index 5aefd8e60..73a36cd2c 100644 --- a/src/components/item/form/EtherpadForm.tsx +++ b/src/components/item/form/EtherpadForm.tsx @@ -1,7 +1,7 @@ -import { TextField, Typography } from '@mui/material'; - import { useState } from 'react'; +import { TextField, Typography } from '@mui/material'; + import { BUILDER } from '@graasp/translations'; import { useBuilderTranslation } from '../../../config/i18n'; diff --git a/src/components/item/header/ItemHeader.tsx b/src/components/item/header/ItemHeader.tsx index ddd6b4fb4..6185407c5 100644 --- a/src/components/item/header/ItemHeader.tsx +++ b/src/components/item/header/ItemHeader.tsx @@ -1,7 +1,7 @@ -import Stack from '@mui/material/Stack'; - import { useMatch } from 'react-router'; +import Stack from '@mui/material/Stack'; + import { Loader } from '@graasp/ui'; import { buildItemPath } from '../../../config/paths'; diff --git a/src/components/item/publish/CCLicenseDialog.js b/src/components/item/publish/CCLicenseDialog.tsx similarity index 80% rename from src/components/item/publish/CCLicenseDialog.js rename to src/components/item/publish/CCLicenseDialog.tsx index 23d2860f9..f20a82fdb 100644 --- a/src/components/item/publish/CCLicenseDialog.js +++ b/src/components/item/publish/CCLicenseDialog.tsx @@ -1,5 +1,3 @@ -import PropTypes from 'prop-types'; - import { Dialog, DialogActions, @@ -14,13 +12,21 @@ import { Button } from '@graasp/ui'; import { useBuilderTranslation } from '../../../config/i18n'; import CancelButton from '../../common/CancelButton'; +type Props = { + open: boolean; + setOpen: (state: boolean) => void; + disabled?: boolean; + buttonName: string; + handleSubmit: () => void; +}; + const CCLicenseDialog = ({ open, setOpen, - disabled, + disabled = false, buttonName, handleSubmit, -}) => { +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const handleClickOpen = () => { @@ -35,7 +41,7 @@ const CCLicenseDialog = ({ <>