diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..dab96e409 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,78 @@ +# XXX See https://docs.codeclimate.com/docs/advanced-configuration +version: "2" +checks: + argument-count: + enabled: true + config: + threshold: 4 + complex-logic: + enabled: true + config: + threshold: 4 + file-lines: + enabled: true + config: + threshold: 400 # 250 by default + method-complexity: + enabled: true + config: + threshold: 10 + method-count: + enabled: true + config: + threshold: 20 + method-lines: + enabled: true + config: + threshold: 100 # 25 by default + nested-control-flow: + enabled: true + config: + threshold: 4 + return-statements: + enabled: true + config: + threshold: 4 + +plugins: +# eslint: # https://docs.codeclimate.com/docs/eslint +# enabled: true +# channel: "eslint-4" # Depends on installed ESLint version - See https://docs.codeclimate.com/docs/eslint#section-eslint-versions + duplication: # https://docs.codeclimate.com/docs/duplication + enabled: true + config: + languages: + javascript: + mass_threshold: 50 # See https://docs.codeclimate.com/docs/duplication#section-understand-the-engine + fixme: # https://docs.codeclimate.com/docs/fixme + enabled: true + config: + strings: # Skip "XXX" as we don't use it for things to fix but rather for highlighting comments (DX) + - FIXME + - BUG + - TODO + - HACK + git-legal: # https://docs.codeclimate.com/docs/git-legal + enabled: true +# tslint: # https://docs.codeclimate.com/docs/tslint +# enabled: true +# config: tslint.json + +# See https://docs.codeclimate.com/docs/excluding-files-and-folders +exclude_patterns: + - "**/*.test.*" + - "**/*.spec.*" + - "src/svg/" + + # Default CC excluded paths: + - "config/" + - "db/" + - "dist/" + - "features/" + - "**/node_modules/" + - "script/" + - "**/spec/" + - "**/test/" + - "**/tests/" + - "**/vendor/" + - "**/*.d.ts" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..15af8104d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,46 @@ +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[{*.jhm, *.xslt, *.xul, *.rng, *.xsl, *.xsd, *.ant, *.tld, *.fxml, *.jrxml, *.xml, *.jnlp, *.wsdl}] +indent_style = space +indent_size = 4 + +[{.babelrc, .prettierrc, .stylelintrc, .eslintrc, jest.config, *.json, *.js, *.js.map, *.ts, *.tsx, *.jsb3, *.jsb2, *.bowerrc, *.graphqlconfig}] +indent_style = space +indent_size = 2 + +[.editorconfig] +indent_style = space +indent_size = 4 + +[*.less] +indent_style = space +indent_size = 2 + +[*.jshintrc] +indent_style = space +indent_size = 2 + +[*.jscsrc] +indent_style = space +indent_size = 2 + +[{tsconfig.lib.json, tsconfig.spec.json, tsconfig.app.json, tsconfig.json, tsconfig.e2e.json}] +indent_style = space +indent_size = 2 + +[*.ejs] +indent_style = space +indent_size = 4 + +[{.analysis_options, *.yml, *.yaml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_size = 4 diff --git a/.env.build.example b/.env.build.example new file mode 100644 index 000000000..c8b374735 --- /dev/null +++ b/.env.build.example @@ -0,0 +1,31 @@ +# XXX Copy this file as ".env.build", will be used only in development stage. For staging/production stages, check the config at "now.CUSTOMER_REF.APP_STAGE.yml". +# XXX Used during the build step in next.config.js +# See https://zeit.co/docs/v2/environment-variables-and-secrets#local-development + +# The environment is "where" the application is running. It can be either "development" (localhost) or "production" (on Zeit's servers) +NODE_ENV=development + +# The stage is "how" the application is running. It can be either "development" (localhost), "staging" or "production" (on Zeit's servers) +APP_STAGE=development + +# The customer that is being deployed +CUSTOMER_REF=customer1 + +# Locize project ID, can be found in the project settings page - Example: 615384ff-0f39-4c7b-89ca-9b0acbfd0869 +LOCIZE_PROJECT_ID= + +# Locize API key, used to automatically save missing translations - Example: 615384ff-0f39-4c7b-89ca-9b0acbfd0869 +# TODO Create a Locize account at +LOCIZE_API_KEY= + +# Amplitude API key, used to report analytics usage - Example: 615384ff0f394c7b89ca9b0acbfd0869 +AMPLITUDE_API_KEY= + +# Sentry DSN, used to report events (errors, etc.) - Example: https://14fa1cae05079675b18cd05403ae5c48@sentry.io/1234567 +SENTRY_DSN= + +# GraphQL API endpoint - XXX We only use one stage due to our plan's limitation, but using two stages is the recommended way +GRAPHQL_API_ENDPOINT=https://api-euwest.graphcms.com/v1/ck73ixhlv09yt01dv2ga1bkbp/master + +# GraphQL API key (GraphCMS auth token) - Readonly because we want everyone to fetch data but not mess with it +GRAPHQL_API_KEY=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImdjbXMtbWFpbi1wcm9kdWN0aW9uIn0.eyJ2ZXJzaW9uIjoyLCJ0b2tlbklkIjoiMGEyZWYwZWYtYmY4Ny00ZmVlLTkzYmQtMzBkMzIzYzAwODM4IiwiaWF0IjoxNTgyNzQ2MDc1LCJpc3MiOiJodHRwczovL21hbmFnZW1lbnQuZ3JhcGhjbXMuY29tLyJ9.wE-IDwFnCNpwUj5AMeGsbf2CGcTgkT1KkE9_l6s-bU03sckdDgV2LXCHH5Fb8XwJGAkoeD5bK9yDnY6oNapiusvg4xRqY-hkRxgh8lB_lTXXpIka6HhjIJz1RQO_dObNGFqr41dKihseBqN5Ce4AJvQBIHyJasKX63xe2eEzdUnWfuNUmrG_XStRu1-xDKKp7vFVox272rr-LqgwRymF3eYcw7J-IAag4qpztoU6zNhKJNYzQKdMMejvMDNg34bHNp4TRIbIUIYpytkwCaAb5TsH98xEiFziU5rUr4EYToSltgE46VnqX2npm56qK-AGp5zauMZgvA20Djtb7BuYqGAqCdOHEQtjjyVLufCu6Y72i9gNQqFQ-WGQ6AFN84KT7BJgRoTruduYG9VhGMOR59HR3jG2QIWXOCt55aI9YwAGNQii0b_QqaoSO08Pb_Ooji5abFLISs70jQb-z1QcnvHIzHnsKqymEWwZhbkxpwf8bv8C6-8k4JGB5YdVj3T_0XQ-OCyvWQIGwVxKysLj8HBeVvXOKUyz7p-thOHbO4qSaRaV7w6_Yy2XtdwBlkIiTTqLezN34vCnsyhZ7N1IgLzp0bwNCyCoPOFs5Q9Ccw7hwJRP3kDT2cW4COJWVt-V5YF_9nnlZN8JjcIgv7FMZKoKRHi004vSosPYGd-v3Uw diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..b955f23b7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +node_modules +.next +src/**/*.test* +src/gql/** +src/propTypes/** +src/svg/** +src/types/** +src/components/svg/** diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 000000000..376c28fae --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,96 @@ +--- +env: + browser: true + commonjs: true + es6: true + node: true +extends: + - plugin:@typescript-eslint/recommended + - plugin:react/recommended + - plugin:jsx-a11y/recommended +globals: + Atomics: readonly + SharedArrayBuffer: readonly +plugins: + - jest + - react + - react-hooks +parser: '@typescript-eslint/parser' +parserOptions: + project: ./tsconfig.json +settings: + react: + version: detect +rules: # See https://eslint.org/docs/rules + semi: + - error + - always # Always put commas, to avoid multilines git diff when new lines are added + quotes: + - error + - single # Prefer simple quotes + - allowTemplateLiterals: true # Allow the use of `` instead of '' and don't try to replace it, even when `` isn't needed + comma-spacing: + - error + - before: false + after: true + indent: + - error + - 2 + - SwitchCase: 1 + arrow-parens: + - error + - always + max-len: 0 # Disable line length checks, because the IDE is already configured to warn about it, and it's a waste of time to check for lines that are too long, especially in comments (like this one!) + strict: 'off' + no-console: 1 # Shouldn't use "console", but "logger" instead + allowArrowFunctions: 0 + no-unused-vars: + - warn # Warn otherwise it false-positive with needed React imports + - args: none # Allow to declare unused variables in function arguments, meant to be used later + import/prefer-default-export: 0 # When there is only a single export from a module, don't enforce a default export, but rather let developer choose what's best + no-else-return: 0 # Don't enforce, let developer choose. Sometimes we like to specifically use "return" for the sake of comprehensibility and avoid ambiguity + no-underscore-dangle: 0 # Allow _ before/after variables and functions, convention for something meant to be "private" + arrow-body-style: 0 # Don't enforce, let developer choose. Sometimes we like to specifically use "return" for ease of debugging and printing + quote-props: + - warn + - consistent-as-needed # Enforce consistency with quotes on props, either all must be quoted, or all unquoted for a given object + no-return-await: 0 # Useful before, but recent node.js enhancements make it useless on node 12+ (we use 10, but still, for consistency) - Read https://stackoverflow.com/questions/44806135/why-no-return-await-vs-const-x-await + no-extra-boolean-cast: 0 # Don't enforce, let developer choose. Using "!!!" is sometimes useful (edge cases), and has a semantic value (dev intention) + object-curly-newline: + - warn + - ObjectExpression: + multiline: true + minProperties: 5 + consistent: true + ObjectPattern: + multiline: true + minProperties: 5 + consistent: true + ImportDeclaration: + multiline: true + minProperties: 8 # Doesn't play so well with webstorm, which wraps based on the number of chars in the row, not based on the number of props #sucks + consistent: true + ExportDeclaration: + multiline: true + minProperties: 5 + consistent: true + react-hooks/rules-of-hooks: error + react-hooks/exhaustive-deps: warn + react/jsx-no-target-blank: warn # Not using "noreferrer" is not a security risk, but "noopener" should always be used indeed + react/prop-types: warn # Should be handled with TS instead + react/no-unescaped-entities: warn # Causes text mismatch when enabled + linebreak-style: + - error + - unix + '@typescript-eslint/ban-ts-ignore': warn # ts-ignore are sometimes the only way to bypass a TS issue, we trust we will use them for good and not abuse them + '@typescript-eslint/no-use-before-define': warn + '@typescript-eslint/no-unused-vars': + - warn + - + vars: 'all' # We don't want unused variables (noise) - XXX Note that this will be a duplicate of "no-unused-vars" rule + args: 'none' # Sometimes it's useful to have unused arguments for later use, such as describing what args are available (DX) + ignoreRestSiblings: true # Sometimes it's useful to have unused props for later use, such as describing what props are available (DX) +overrides: + - files: ['**/*.tsx'] + rules: + 'react/prop-types': 'off' diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000..3a2b18ab8 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,105 @@ +# Github Actions <> Zeit integrations + +> Automated actions configured through GitHub Actions +> +> This documentation explains how our GitHub actions integrate themselves with the Zeit platform + +--- + +## Getting started + +### Introducing GitHub Actions + +> Stuff to read if you're not familiar with GitHub Actions + +[Official documentation](https://help.github.com/en/actions/automating-your-workflow-with-github-actions) + +Most useful documentation links: +- [https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows) +- [https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#on](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#on) +- [https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idneeds](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idneeds) +- [https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-on](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-on) +- [https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet) + + +### Requirements + +> List of necessary requirements for the `Github Actions <> Zeit` to work properly. + +#### Required GitHub secrets: + +> Those secrets must be added on Github settings page, such as https://github.com/UnlyEd/next-right-now/settings/secrets + +- `GITHUB_CI_PR_COMMENT`: Allows to post comments on GitHub Pull Request - See [https://github.com/settings/tokens](https://github.com/settings/tokens) + GitHub **["personal access token"](https://github.com/settings/tokens)** from your personal account with the following permission scopes: + - `repo` (all) + - `user` + - `read:user` + - `user:email` + - `workflow` + +- `ZEIT_TOKEN`: Allows to trigger deployments - See [https://zeit.co/account/tokens](https://zeit.co/account/tokens) + Zeit personal token + +--- + +## Overview +### Stages + +> We use two different stages. Each stage is meant to use a different configuration. + +_**staging**_ (see [`deploy-zeit-staging`](./deploy-zeit-staging.yml)): +Every pushed commit, (except those made on `master`) automatically starts a new Zeit deployment, using the related staging configuration file. +You can choose which client you deploy by changing the symbolic link `now.json` file. +Changing the symlink allows to change which "CUSTOMER_REF" gets automatically deployed. + +_**production**_ (see [`deploy-zeit-production`](./deploy-zeit-production.yml)): +Commits pushed to the `master` branch will automatically deploy the "CUSTOMER_REF" specified in `now.json` to Zeit, but will use it's production configuration. + +> N.B: Those events are triggered by pushed commit, but also merged branches. + +### GitHub Actions Jobs workflow + +> The same workflow is used for both stages. The main difference is the trigger. +> +> A `production` deployment is **triggered by any change** in the `master` branch, +> while a `staging` deployment is **triggered by any change that is not on** the `master` branch. + +**Jobs workflow:** +* Installing Node.js and npm dependencies, by specifying Node version +* Deploy code: + * We checkout to the last branch commit, documentation [here](https://github.com/cypress-io/github-action) + * We parse current `now.json` config file to get `CUSTOMER_REF`, which corresponding to customer project to deploy, and then we run `yarn deploy:CUSTOMER_REF` or in production `yarn deploy:CUSTOMER_REF:production` +* Run 2e2 tests: + * We need to checkout again (because the code is not persistent) + * We ask to Zeit api for the last deployment data, retrieve the url and then set it as environment variable as `ZEIT_DEPLOYMENT_URL` (to be able to use it afterwards) + * We use default action provided by cypress (documentation [here](https://github.com/cypress-io/github-action)): + * _**wait-on**_: Allows us to wait before starting tests. It ping the endpoint until it's up, with a timeout of 60 seconds per default. + * _**config-file**_: We need to specify a config file because cypress is looking for cypress.json in the main folder. + The config file itself doesn't matter because we will override most settings anyway. We just need `projectId` to run the tests. + * _**config**_: Overrides some default config, like the `baseUrl` in particular (we use the `ZEIT_DEPLOYMENT_URL` instead) + * We upload artifacts on tests failure, more documentation [here](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts) + +--- + +## Dependencies + +### Actions +* _**[actions/setup-node@v1](https://github.com/actions/setup-node)**_: + Setup node.js and install dependencies +* _**[actions/checkout@v1](https://github.com/cypress-io/github-action)**_: + Checkout to last commit to retrieve code +* _**[cypress-io/github-action@v1](https://github.com/cypress-io/github-action)**_: + Run Cypress tests +* _**[actions/upload-artifact@v1](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts)**_: + Upload artifacts to Github + +--- + +## Complex commands +* _**[jq](https://cameronnokes.com/blog/working-with-json-in-bash-using-jq/)**_: + JSON parser for bash +* _**[tr](http://linuxcommand.org/lc3_man_pages/tr1.html)**_: + Bash editor, used to remove characters +* _**[echo "::set-env name=key::value"](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions)**_: + Set env variable for all others jobs diff --git a/.github/workflows/deploy-zeit-production.yml b/.github/workflows/deploy-zeit-production.yml new file mode 100644 index 000000000..e017fa8d1 --- /dev/null +++ b/.github/workflows/deploy-zeit-production.yml @@ -0,0 +1,117 @@ +# Summary: +# Creates a new deployment on Zeit's platform, when anything is pushed in any branch (except for the "master" branch). +# Read ./README.md for extensive documentation + +name: Deploy to Zeit (production) + +on: + push: + branches: + - 'master' + +jobs: + # Configures the deployment environment, install dependencies (like node, npm, etc.) that are requirements for the upcoming jobs + # Ex: Necessary to run `yarn deploy` + setup-environment: + name: Setup deployment environment (Ubuntu 18.04 - Node 10.x) + runs-on: ubuntu-18.04 + steps: + - name: Installing node.js + uses: actions/setup-node@v1 # Used to install node environment - XXX https://github.com/actions/setup-node + with: + node-version: '10.x' # Use the same node.js version as the one Zeit's uses (currently node10.x) + + # Starts a Zeit deployment, using the production configuration file of the default institution + # The default institution is the one defined in the `now.json` file (which is a symlink to the actual file) + # N.B: It's Zeit that will perform the actual deployment + start-production-deployment: + name: Starts Zeit deployment (production) (Ubuntu 18.04) + runs-on: ubuntu-18.04 + needs: setup-environment + steps: + - uses: actions/checkout@v1 # Get last commit pushed - XXX See https://github.com/actions/checkout + - name: Deploying on Zeit + run: yarn deploy:$(cat now.json | jq -r '.build.env.CUSTOMER_REF'):production --token $ZEIT_TOKEN + env: + ZEIT_TOKEN: ${{ secrets.ZEIT_TOKEN }} # Passing github's secret to the worker + + # Runs E2E tests against the Zeit deployment + run-2e2-tests: + name: Run end to end (E2E) tests (Ubuntu 18.04) + runs-on: ubuntu-18.04 + # Docker image with Cypress pre-installed + # https://github.com/cypress-io/cypress-docker-images/tree/master/included + container: cypress/included:3.8.3 + needs: start-production-deployment + steps: + - uses: actions/checkout@v1 # Get last commit pushed - XXX See https://github.com/actions/checkout + - name: Resolving deployment url from Zeit + # The following workflow is: + # - getting all deployments data (by using the scope in `now.json`) + # - then we get the last url (in Node.js it corresponds as `response.deployments[0].url` + # - and then we remove the `"` character to pre-format url + # We need to set env the url for next step, formatted as `https://${url provided by API}` + run: | + apt update -y >/dev/null && apt install -y jq >/dev/null + ZEIT_DEPLOYMENT=`curl -H 'Accept: application/json' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${{ secrets.ZEIT_TOKEN }}' https://api.zeit.co/v5/now/deployments?teamId=$(cat now.json | jq -r '.scope') | jq '.deployments [0].url' | tr -d \"` + echo "::set-env name=ZEIT_DEPLOYMENT_URL::https://$ZEIT_DEPLOYMENT" + + # On deployment failure, add a comment to the PR + - name: Comment PR (Deployment failure) + uses: unsplash/comment-on-pr@master + if: failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nDeployment FAILED\n\t Commit ${{ github.sha }} failed to deploy to ${{ env.ZEIT_DEPLOYMENT_URL }} (click to see logs)" + + # On deployment success, add a comment to the PR + - name: Comment PR (Deployment success) + uses: unsplash/comment-on-pr@master + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nDeployment SUCCESS\n\t Commit ${{ github.sha }} successfully deployed to ${{ env.ZEIT_DEPLOYMENT_URL }}" + + # Run the E2E tests against the new Zeit deployment + - name: Run E2E tests (Cypress) + uses: cypress-io/github-action@v1 # XXX See https://github.com/cypress-io/github-action + with: + wait-on: ${{ env.ZEIT_DEPLOYMENT_URL }} # Be sure that the endpoint is ready by pinging it before starting tests, it has a timeout of 60seconds + config-file: cypress/config-customer1.json # The config file itself doesn't matter because we will override most settings anyway. We just need `projectId` to run the tests. + config: baseUrl=${{ env.ZEIT_DEPLOYMENT_URL }} # Overriding baseUrl provided by config file to test the new deployment + + # On E2E failure, upload screenshots + - name: Uplad screenshots artifacts (E2E failure) + uses: actions/upload-artifact@v1 # On failure we upload artifacts, https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts + if: failure() + with: + name: screenshots + path: cypress/screenshots/ + + # On E2E failure, upload videos + - name: Uplad videos artifacts (E2E failure) + uses: actions/upload-artifact@v1 # On failure we upload artifacts, https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts + if: failure() + with: + name: videos + path: cypress/videos/ + + # On E2E failure, add a comment to the PR with additional information + - name: Comment PR (E2E failure) + uses: unsplash/comment-on-pr@master + if: failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nE2E tests FAILED\n Download artifacts (screenshots + videos) from `checks` section at the top" + + # On E2E success, add a comment to the PR + - name: Comment PR (E2E success) + uses: unsplash/comment-on-pr@master + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nE2E tests SUCCESS" diff --git a/.github/workflows/deploy-zeit-staging.yml b/.github/workflows/deploy-zeit-staging.yml new file mode 100644 index 000000000..93654a614 --- /dev/null +++ b/.github/workflows/deploy-zeit-staging.yml @@ -0,0 +1,125 @@ +# Summary: +# Creates a new deployment on Zeit's platform, when anything is pushed in any branch (except for the "master" branch). +# Read ./README.md for extensive documentation + +name: Deploy to Zeit (staging) + +on: + push: + branches-ignore: + - 'master' + +jobs: + # Configures the deployment environment, install dependencies (like node, npm, etc.) that are requirements for the upcoming jobs + # Ex: Necessary to run `yarn deploy` + setup-environment: + name: Setup deployment environment (Ubuntu 18.04 - Node 10.x) + runs-on: ubuntu-18.04 + steps: + - name: Installing node.js + uses: actions/setup-node@v1 # Used to install node environment - XXX https://github.com/actions/setup-node + with: + node-version: '10.x' # Use the same node.js version as the one Zeit's uses (currently node10.x) + + # Starts a Zeit deployment, using the staging configuration file of the default institution + # The default institution is the one defined in the `now.json` file (which is a symlink to the actual file) + # N.B: It's Zeit that will perform the actual deployment + start-staging-deployment: + name: Starts Zeit deployment (staging) (Ubuntu 18.04) + runs-on: ubuntu-18.04 + needs: setup-environment + steps: + - uses: actions/checkout@v1 # Get last commit pushed - XXX See https://github.com/actions/checkout + - name: Deploying on Zeit + run: yarn deploy:$(cat now.json | jq -r '.build.env.CUSTOMER_REF') --token $ZEIT_TOKEN + env: + ZEIT_TOKEN: ${{ secrets.ZEIT_TOKEN }} # Passing github's secret to the worker + + # Runs E2E tests against the Zeit deployment + run-2e2-tests: + name: Run end to end (E2E) tests (Ubuntu 18.04) + runs-on: ubuntu-18.04 + # Docker image with Cypress pre-installed + # https://github.com/cypress-io/cypress-docker-images/tree/master/included + container: cypress/included:3.8.3 + needs: start-staging-deployment + steps: + - uses: actions/checkout@v1 # Get last commit pushed - XXX See https://github.com/actions/checkout + - name: Resolving deployment url from Zeit + # The following workflow is: + # - getting all deployments data (by using the scope in `now.json`) + # - then we get the last url (in Node.js it corresponds as `response.deployments[0].url` + # - and then we remove the `"` character to pre-format url + # We need to set env the url for next step, formatted as `https://${url provided by API}` + run: | + apt update -y >/dev/null && apt install -y jq >/dev/null + ZEIT_DEPLOYMENT=`curl -H 'Accept: application/json' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${{ secrets.ZEIT_TOKEN }}' https://api.zeit.co/v5/now/deployments?teamId=$(cat now.json | jq -r '.scope') | jq '.deployments [0].url' | tr -d \"` + echo "::set-env name=ZEIT_DEPLOYMENT_URL::https://$ZEIT_DEPLOYMENT" + + ZEIT_DEPLOYEMENT_ALIAS=$(cat now.json | jq -r '.name')-${CURRENT_BRANCH##*/}.now.sh + echo "::set-env name=ZEIT_DEPLOYEMENT_ALIAS::https://$ZEIT_DEPLOYEMENT_ALIAS" + + npx now alias https://$ZEIT_DEPLOYMENT https://$ZEIT_DEPLOYEMENT_ALIAS --token $ZEIT_TOKEN + env: + ZEIT_TOKEN: ${{ secrets.ZEIT_TOKEN }} # Passing github's secret to the worker + CURRENT_BRANCH: ${{ github.ref }} # Passing current branch to worker + + # On deployment failure, add a comment to the PR + - name: Comment PR (Deployment failure) + uses: unsplash/comment-on-pr@master + if: failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nDeployment FAILED\n\t Commit ${{ github.sha }} failed to deploy to ${{ env.ZEIT_DEPLOYMENT_URL }} (click to see logs)" + + # On deployment success, add a comment to the PR + - name: Comment PR (Deployment success) + uses: unsplash/comment-on-pr@master + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nDeployment SUCCESS\n\t Commit ${{ github.sha }} successfully deployed to ${{ env.ZEIT_DEPLOYMENT_URL }}\n\tDeployment aliased as ${{ env.ZEIT_DEPLOYEMENT_ALIAS }}" + + # Run the E2E tests against the new Zeit deployment + - name: Run E2E tests (Cypress) + uses: cypress-io/github-action@v1 # XXX See https://github.com/cypress-io/github-action + with: + wait-on: ${{ env.ZEIT_DEPLOYMENT_URL }} # Be sure that the endpoint is ready by pinging it before starting tests, it has a timeout of 60seconds + config-file: cypress/config-customer1.json # The config file itself doesn't matter because we will override most settings anyway. We just need `projectId` to run the tests. + config: baseUrl=${{ env.ZEIT_DEPLOYMENT_URL }} # Overriding baseUrl provided by config file to test the new deployment + + # On E2E failure, upload screenshots + - name: Uplad screenshots artifacts (E2E failure) + uses: actions/upload-artifact@v1 # On failure we upload artifacts, https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts + if: failure() + with: + name: screenshots + path: cypress/screenshots/ + + # On E2E failure, upload videos + - name: Uplad videos artifacts (E2E failure) + uses: actions/upload-artifact@v1 # On failure we upload artifacts, https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts + if: failure() + with: + name: videos + path: cypress/videos/ + + # On E2E failure, add a comment to the PR with additional information + - name: Comment PR (E2E failure) + uses: unsplash/comment-on-pr@master + if: failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nE2E tests FAILED\n Download artifacts (screenshots + videos) from `checks` section at the top" + + # On E2E success, add a comment to the PR + - name: Comment PR (E2E success) + uses: unsplash/comment-on-pr@master + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_CI_PR_COMMENT }} + with: + msg: "[GitHub Actions]\nE2E tests SUCCESS" diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c88f265f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,135 @@ +# Created by https://www.gitignore.io/api/webstorm + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +### WebStorm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + + +# End of https://www.gitignore.io/api/webstorm + +######################### CUSTOM/MANUAL ############################# + +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# IDE plugins +.idea/markdown-navigator*/** + +# package directories +node_modules +jspm_packages + +# Serverless directories +.serverless +.webpack +.next +dist + +.DS_Store +.sls-simulate-registry + +# Builds +build +.firebase +coverage/ + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# misc +.env* +!.env.build.example + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# cypress - tmp files +cypress/screenshots +cypress/videos + +# svg compiled into js files +src/svg/*.js +src/svg/*.tsx + +# Zeit +.now diff --git a/.graphqlconfig b/.graphqlconfig new file mode 100644 index 000000000..ae939895e --- /dev/null +++ b/.graphqlconfig @@ -0,0 +1,15 @@ +{ + "schemaPath": "schema.graphql", + "extensions": { + "endpoints": { + "master": { + "url": "https://api-euwest.graphcms.com/v1/ck73ixhlv09yt01dv2ga1bkbp/master", + "introspect": true, + "headers": { + "user-agent": "JS GraphQL", + "Authorization": "Bearer ${env:GRAPHQL_API_KEY}" + } + } + } + } +} diff --git a/.idea/runConfigurations/Debug.xml b/.idea/runConfigurations/Debug.xml new file mode 100644 index 000000000..bc79bb622 --- /dev/null +++ b/.idea/runConfigurations/Debug.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.nowignore b/.nowignore new file mode 100644 index 000000000..4cbc376fd --- /dev/null +++ b/.nowignore @@ -0,0 +1,14 @@ +# Optimize speed/size of deployments by not uploading those files +.idea/ +node_modules/ +cypress/ +coverage/ +.codeclimate.yml +.editorconfig +.env.build.example +.graphqlconfig +buildspec.yml +README.md +schema.graphql +yarn.lock +yarn-error.log diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..c095bf0f4 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v10.17.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5c30f89b3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +Changelog +=== + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..7e9d18513 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2019 Unly + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index b285a037f..4ee8cb9e7 100644 --- a/README.md +++ b/README.md @@ -1 +1,487 @@ -# next-right-now \ No newline at end of file +Unly logo +[![Maintainability](https://api.codeclimate.com/v1/badges/3f3f2c0a4106abcb9a1d/maintainability)](https://codeclimate.com/github/UnlyEd/next-right-now/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/3f3f2c0a4106abcb9a1d/test_coverage)](https://codeclimate.com/github/UnlyEd/next-right-now/test_coverage) +[![Locize Latest version](https://img.shields.io/badge/dynamic/json.svg?style=plastic&color=2096F3&label=locize&query=%24.versions%5B%27latest%27%5D.translatedPercentage&url=https://api.locize.io/badgedata/7867a172-62dc-4f47-b33c-1785c4701b12?t=1573424617493&suffix=%+translated&link=https://www.locize.com&prefix=latest:+)](https://www.locize.io/p/w7jrmdie/statistics/badges) +[![Locize Production version](https://img.shields.io/badge/dynamic/json.svg?style=plastic&color=2096F3&label=locize&query=%24.versions%5B%27production%27%5D.translatedPercentage&url=https://api.locize.io/badgedata/7867a172-62dc-4f47-b33c-1785c4701b12?t=1573424617493&suffix=%+translated&link=https://www.locize.com&prefix=production:+)](https://www.locize.io/p/w7jrmdie/statistics/badges) + +Next Right Now +=== + +> Next Right Now (NRN) is meant to be used as a boilerplate for quick getting started with a production-grade project featuring the Next.js framework, hosted on Zeit platform. +> +> NRN is strongly opinionated, but also very flexible. +> It can also be used as a learning/teaching resource. +> +> A lot of built-in components and utilities are available out of the box. Keep those you like, throw what you dislike, rewrite what doesn't exactly fit your needs, and share what could be useful to others! ;) +> +> Don't hesitate to share your opinion and propose improvements + +Our goal, by releasing NRN, is to allow any developer to quickly getting started with a fully working setup that includes many of the "must-have" features any real project needs as of 2020, such as: +- **B2B multi-tenants** first-class support (optional, advanced use-case) + - Supports configuration, deployment, testing, monitoring of multiple customers through the same project (identical code base, multi-tenants design) + - This is a very special consideration, and required quite a lot of efforts to make it works smoothly + - Most projects do not need such capability, but we build our own projects with such requirement in mind, and thus released NRN with such built-in capability + - It's very easy not to use it if you don't need to, but if you do then it'll be a huge time saver for you! +- Built-in **stages** (development, staging, production) workflow +- **TypeScript** first-class support +- **GraphQL** support (thanks to [Apollo](https://github.com/apollographql/apollo-client), and others) + - **GraphCMS** first-class support, which hosts our GraphQL API (server) and database, fully hosted (thanks to [GraphCMS1](https://graphcms.com/?ref=unly-nrn)) + - **GraphQL schema** available in the developer environment (thanks to [GraphQL Config](https://github.com/kamilkisiela/graphql-config)) +- **SSR** and **CSR** capabilities (thanks to the [Next.js framework](https://nextjs.org/)) +- React hooks over HOC (functional components over classes) +- **Internationalisation** (i18n) first-class support (SSR + CSR friendly) (thanks to [react-i18next](https://react.i18next.com/)) + - I18n of the database (thanks to [GraphCMS1](https://graphcms.com/?ref=unly-nrn)) + - [Automated fallback language, through HTTP headers](https://graphcms.com/features/content-localization/?ref=unly-nrn) + - I18n of the project (thanks to [Locize1](https://locize.com/?lng=en)) + - [Automated fallback language](https://www.i18next.com/principles/fallback) + - [In-context editor](https://docs.locize.com/more/incontext-editor) + - Auto-add i18n keys with default translation when working locally +- **Testing** first-class support + - TS-friendly (thanks to [ts-jest](https://github.com/kulshekhar/ts-jest)) + - End-to-end (E2E) tests (thanks to [Cypress](https://www.cypress.io/)) + - Other tests, such as unit tests, etc. (thanks to [Jest](https://jestjs.io/), [Jest extended](https://github.com/jest-community/jest-extended)) +- Strong **observability** of the system (monitoring) and push-notification on your own messaging channel (i.e: Slack) when things go wrong (thanks to [Sentry1](https://sentry.io/)) +- **Universal JS**, to re-use code as much as possible between frontend and backend (i.e: universal cookies API) +- Powerful **CSS-in-JS** styles, SSR & CSR friendly, JSX-friendly, styled-component friendly (thanks to [Emotion](https://github.com/emotion-js/emotion)) +- **Font** first-class support (SSR/CSR friendly, no FOUT effect) (thanks to [WebFontLoader](https://github.com/typekit/webfontloader)) +- Fine-grained frontend **analytics**, react-friendly, flexible, SPA-friendly (thanks to [Amplitude1**2**](https://amplitude.com/)) +- **Integrated CI/CD pipeline**, automated deployments to preview domains and production domains (thanks to the [Zeit](https://zeit.co/), [GitHub Actions](https://github.com/features/actions)) + - Including a dedicated "per-deployment domain", for fast feedback loop and testing means, in an online environment (staging) +- Built-in **utilities** + - Convert SVG to TSX components (thanks to [SVGR](https://github.com/gregberge/svgr)) + - Font Awesome icons as react components, with SSR support (thanks to [Font Awesome](https://github.com/FortAwesome/react-fontawesome)) + - Bootstrap support (thanks to [Reactstrap](https://reactstrap.github.io/)) + - Node debug mode for the server side (only built-in on WebStorm) + - NPM security audit (script) + - NPM developer-friendly outdated packages (script) + - Display warning on outdated browsers, works even if bundle isn't ES5 compatible (IE11, etc.) +- [Fully documented usage of all our third party NPM libraries (AKA dependencies)](./README_DEPENDENCIES.md) + +**Legend:** +- `first-class support`: Means that we took a very special care to support this, and that it's not as simple as one may believe +- 1: Third parties that provide a free plan that is enough for a "simple" application, but make sure to check that their pricing fits you. +- **2**: Beware huge gap between free and paid plans cost. + +--- + + + +- [Getting started](#getting-started) + * [Introduction videos](#introduction-videos) + + [Part 1 - Overview of Next Right Now](#part-1---overview-of-next-right-now) + + [Part 2 - Developer Experience with Next Right Now](#part-2---developer-experience-with-next-right-now) + + [Guides](#guides) + * [Showcases - Live demo](#showcases---live-demo) + * [Local installation](#local-installation) + * [Configuring Zeit staging/production stages](#configuring-zeit-stagingproduction-stages) + + [Configuring your own Zeit account](#configuring-your-own-zeit-account) + + [Configuring secrets](#configuring-secrets) + + [**Advanced** - When using multiple customers (B2B multi-tenants)](#advanced---when-using-multiple-customers-b2b-multi-tenants) +- [Understanding `Environments` and `Stages`](#understanding-environments-and-stages) + * [What is an `environment`?](#what-is-an-environment) + * [What is a `stage`?](#what-is-a-stage) +- [Deploying on Zeit (manually)](#deploying-on-zeit-manually) + * [Staging deployments](#staging-deployments) + * [Production deployment](#production-deployment) +- [Testing](#testing) + * [CI tests Workflow](#ci-tests-workflow) + * [Running tests manually (locally)](#running-tests-manually-locally) + * [Running E2E tests manually (locally)](#running-e2e-tests-manually-locally) +- [I18n (Internationalization)](#i18n-internationalization) + * [Fetching translations through GraphCMS](#fetching-translations-through-graphcms) + * [Fetching translations through Locize provider](#fetching-translations-through-locize-provider) + + [Locize translation workflow in-depth](#locize-translation-workflow-in-depth) + - [Locize additional services](#locize-additional-services) + - [Other additional services](#other-additional-services) +- [GraphCMS](#graphcms) + * [Discount](#discount) + * [Fetching data from GraphCMS](#fetching-data-from-graphcms) +- [Amplitude](#amplitude) + * [Chrome developer debug tool](#chrome-developer-debug-tool) +- [Continuous Integration & Continuous Deployment (CI/CD)](#continuous-integration--continuous-deployment-cicd) + * [Overview](#overview) + * [Workflow of our Zeit <> Github Actions integration](#workflow-of-our-zeit--github-actions-integration) + * [In-depth project's dependencies](#in-depth-projects-dependencies) + * [License](#license) +- [Vulnerability disclosure](#vulnerability-disclosure) +- [Contributors and maintainers](#contributors-and-maintainers) +- [**[ABOUT UNLY]**](#about-unly-) + + + +--- + +# Getting started + +## Introduction videos + +### Part 1 - Overview of Next Right Now +[![Part 1 - Overview of Next Right Now](https://img.youtube.com/vi/kltkFwnFL-k/maxresdefault.jpg)](http://youtu.be/kltkFwnFL-k?hd=1) + +> Let's talk about why we built RNR in the first place, how it's meant to be used, whom it is for. +> +> This video features Zeit deployments, i18n, GraphCMS, Locize in-context editor, Sentry monitoring, Amplitude analytics, CI/CD Github Actions + +### Part 2 - Developer Experience with Next Right Now +[![Part 2 - Developer Experience with Next Right Now](https://img.youtube.com/vi/fGlgIEeUqFg/maxresdefault.jpg)](http://youtu.be/fGlgIEeUqFg?hd=1) + +> Let's talk about the developer experience (DX) provided by NRN and how it helps being more efficient. +> +> This video features GraphQL auto-completion and local schema update, deployment workflow, CI/CD Github Actions explanations, interactive E2E testing, GraphsCMS field creation + +### Guides +- [How to run NRN in debug mode using WebStorm debug configuration](http://youtu.be/3vbkiRAT4e8?hd=1) + +## Showcases - Live demo + +You can see 2 almost identical demo at: +- [https://nrn-customer1.now.sh](https://nrn-customer1.now.sh) +- [https://nrn-customer2.now.sh](https://nrn-customer2.now.sh) + +Both share the same source code and configuration, but the database content is different (hosted on GraphCMS). + +> **Tip**: You can get metadata at [/api/status](https://nrn-customer1.now.sh/api/status) +> +> **Tip**: You can go to preview deployments (staging stage) by looking at [this PR comments](https://github.com/UnlyEd/next-right-now/pull/1) + +## Local installation + +> This assumes you've cloned the project on your own computer. + +- `nvm use` - Selects the right node.js version based on our [`.nvmrc`](./.nvmrc) file +- `yarn` - Installs all deps from [`package.json`](./package.json) +- Duplicate the [`.env.build.example`](./.env.build.example) and rename it `.env.build` _(this file is only used on your local computer)_ + 1. Create a free account on [Locize](https://www.locize.app/register?ref=unly-nrn), it's required for the project to work properly when running locally + - Make sure to keep `i18n format: i18next/locizify` (by default) + - Make sure to select `publish format: json nested` + 1. Copy the `Project Id` and `API Key` from the settings page to the `.env.build` `LOCIZE_PROJECT_ID` and `LOCIZE_API_KEY` respectively +- `yarn start` - Starts the app on [http://localhost:8888/](http://localhost:8888/) +- That's it! + +> **Tip**: You can enable in-context editor mode in order to localise your static content, by appending `?locize=true` to the url, see [https://nrn-customer1.now.sh/?locize=true](https://nrn-customer1.now.sh/?locize=true) _(this is only enabled in development and staging stages, not in production)_ +> +> **Tip**: You can start the project in debug mode (built-in for WebStorm only) [by running the WebStorm "Debug" configuration in debug mode](https://youtu.be/3vbkiRAT4e8) + +## Configuring Zeit staging/production stages + +> Your setup works locally, but now you want to deploy it online. We will do so by hosting it on the Zeit platform. + +### Configuring your own Zeit account + +1. Go to [https://zeit.co/signup](https://zeit.co/signup?ref=unly-nrn) and create your account, a free account will do just fine for now +1. _(Optional)_ Run `npx now login` and follow the login steps (this will allow you to use the Now CLI locally and deploy to your Zeit account). + + +### Configuring secrets + +1. Configuring [Now Secrets](https://zeit.co/docs/v2/serverless-functions/env-and-secrets?query=secre#adding-secrets), open [`now.customer1.staging.json`](./now.customer1.staging.json) + - Each secret must be added manually, one by one + - A secret starts with `@` + - Secrets are global to the whole team, that's why they're all prefixed by `nrn-`, so that they don't conflict with other projects _(Zeit is working on this, to avoid leaking secrets from one project to another, but hasn't released anything yet)_ + - Take a look at [.env.build.example](.env.build.example) for secrets to use by default (you can use them for testing purpose, it's fine, they're not sensitive) + - Example: `now secrets add nrn-locize-project-id 658fc999-dfa8-4307-b9d7-b4870ad5b968` +1. **Advanced** - If you want to deploy multiple customers, you will need to add `GITHUB_CI_PR_COMMENT` and `ZEIT_TOKEN` **Github secrets** as well. [See "Required GitHub secrets"](./.github/workflows/README.md). + +> If you don't provide all secrets, the app will not be deployable. The Now CLI will complain about missing secrets and abort the build. + +If you don't want need to deploy your app for multiple clients, then you should delete the whole [.github](.github) folder, as you won't need it. +Zeit native Github integration will do just fine for that simpler use-case! :) + +You can now commit/push any change, which will trigger a new build, **through the native Zeit <> Github integration.** + +> Once you push, Zeit will automatically create a project using your repository's name. For instance, it created a "next-right-now" project for us. +> You can also deploy from your local computer (without using CI), see [Deploying on Zeit (manually)](#deploying-on-zeit-manually) + +The project "next-right-now" is created by the native Zeit <> Github integration. +It's perfectly fine if you **don't need** to deploy multiple customers (multi-tenants). + +### **Advanced** - When using multiple customers (B2B multi-tenants) + +If you want to deploy multiple customers, then the default "next-right-now" project it won't help you at all. +In that case you should unlink the Zeit project from your Github repository and then delete the "next-right-now" project and leave Github Action handle that (it will create a "nrn-customer1" project when deploying that customer for the first time). + +Once unlinked and deleted, Zeit will not automatically re-create the "next-right-now" project later on, and each customer will live on its own project ("nrn-customer1", "nrn-customer1", etc.). + +You can now commit/push any change, which will trigger a new build, through **Github Actions.** + +--- + +# Understanding `Environments` and `Stages` + +> The application relies on environment variables to function correctly. +> Those variables are provided differently depending on the environment. + +When working on the `development` environment (localhost), the variables from [`.env.build`](.env.build) are used by [the webpack configuration](./next.config.js), +also, the [`now.json`](./now.json) configuration file is used _(it's a symlink to [`now.customer1.staging.json`](now.customer1.staging.json))_, but the variable defined in `.env.build` take precedence. + +When deploying an instance to the Zeit's platform, the variables used are the one that belong to that instance, such as: +- `yarn deploy:customer1`: This script will deploy an instance using the [`now.customer1.production.json`](now.customer1.staging.json) file. +- `yarn deploy:customer1:production`: This script will deploy an instance using the [`now.customer1.production.json`](now.customer1.production.json) file. + +> In those files, it's the `build.env` part that is used at build time (build is done on Zeit), which basically replaces all references of every environment variable by the actual value (string replace). + +## What is an `environment`? + +> An environment is "where" the application is running. +> It can be either "development" (localhost) or "production" (on Zeit's servers). +> +> **The `environment` is defined by the `NODE_ENV` environment variable.** +> +> **N.B**: It is not possible to any other value, [as enforced by Next](https://github.com/zeit/next.js/blob/master/errors/env-key-not-allowed.md) + +When working on your local computer, you automatically use `NODE_ENV=developement`. + +The environment affects how the application **is bundled**, it is used at **build time**. (webpack, hot-reloading, etc.) + +> i.e: In `development` environment, you have access to PropTypes warnings, but you won't in `production`. + +## What is a `stage`? + +> A stage is "how" the application is running. +> It can be either "development" (localhost), "staging" or "production" (on Zeit's servers) - _You can add more if you need_ +> +> **The `stage` is defined by the `APP_STAGE` environment variable.** +> +> **N.B**: You can use any stage name you like, there is no restriction. + +- When working on your local computer, you automatically use `APP_STAGE=developement`. +- When creating a Zeit preview deployment (i.e: when pushing a commit/branch (CD), when using `yarn deploy`, etc.), you automatically use `APP_STAGE=staging`. +- When creating a Zeit production deployment (i.e: when using `yarn deploy:customer1:production`, when merging a PR to `master`, etc.), you automatically use `APP_STAGE=production`. + +The stage changes the behaviour of the application, because we sometimes need the application to behave differently depending on the stage. + +> i.e: In `production` stage, the Locize configuration uses the `production` version. +> When using another stage, it uses the `latest` version. + +> i.e: We don't want to enable the same level of debugging in production environment. +> For instance, Locize is configured to be in `debug` mode only in non-production stages. + +--- + +# Deploying on Zeit (manually) + +For each customer instance, we create a different Zeit project. + +A project is itself composed of multiple staging deployments (called "previews" on Zeit) and one production deployment. + +_**N.B**: If you want to learn more about what happens (on the technical level) when pushing a commit to the repository, read the [CI/CD section](#continuous-integration--continuous-deployment-cicd)_ + +## Staging deployments + +Using Zeit Now, **we have access to many staging "deployments"**. + +By default, there is one custom domain per Git Branch, but also one per commit. +It is also possible to create a custom domain manually from the CLI, for any deployment. + +- `yarn deploy` - Deploy the customer1 app (shortcut) +- `yarn deploy:customer1` - Deploy the customer1 app +- `yarn deploy:customer2` - Deploy the customer2 app +- `yarn deploy:all` - Deploy all apps + +## Production deployment + +While there can be multiple staging deployments, **there is only one production deployment (per project)** + +- `yarn deploy:customer1:production` - Deploy the customer1 app to [https://zeit.co/unly/nrn-customer1](https://zeit.co/unly/nrn-customer1) +- `yarn deploy:customer2:production` - Deploy the customer2 app to [https://zeit.co/unly/nrn-customer2](https://zeit.co/unly/nrn-customer2) +- `yarn deploy:all:production` - Deploy all apps + +> N.B: Those commands use the `now` command with the `--prod` argument behind the wheel. +> N.B: Those commands are used by our CI server. + +--- + +# Testing + +## CI tests Workflow + +Zeit will automatically run the tests before deploying, as configured in the `yarn build` command. + +> If any test fail, the deployment will be aborted. This ensures that any code that doesn't pass the tests never get deployed online. + +Once a deployment has been deployed on Zeit, **Github Actions** will run our **E2E tests**, to make sure that the app behaves as expected. +This can also be considered as an integration tests suite. + +## Running tests manually (locally) +You can run interactive tests using Jest with `yarn test` script. + +## Running E2E tests manually (locally) +You can run interactive E2E tests using Cypress with `yarn e2e:open` script. + +You can also run them non-interactively using `yarn e2e:run` script. + +> You may need to run `yarn e2e:install` script first + +--- + +# I18n (Internationalization) + +The content displayed on NRN is translated using different ways, depending on where the translations are stored: +- GraphCMS - Dynamic content (fetched from the DB, through GraphCMS API). This content can be updated through GraphCMS backoffice. +- Locize - Static content (fetched from Locize API). This content can be updated through Locize backoffice, or when using in-context editor. + +## Fetching translations through GraphCMS + +> When the content is fetched through GraphCMS, the content is automatically internationalized using the `gcms-locale` **HTTP header** provided in the HTTP request. +> +> This means that a given user will not fetch the same content (even though the GraphQL query is identical) based on the `gcms-locale` value. + +The `gcms-locale` value is a string of the form `'FR, EN'`. _([case matters!](https://graphcms.com/docs/api/content-api/?ref=unly-nrn#passing-a-header-flag))_ +There is two locales defined in this example _(there can be more, but we only handle 2 locales at this time in this app)_. +- `FR` is the first and main locale. Content in French will therefore be loaded first. +- `EN` is the second and is a fallback locale. If the content is not found in the main locale, then fallback locales are used. + +See the [official documentation](https://graphcms.com/docs/api/content-api/?ref=unly-nrn#passing-a-header-flag) to learn more. + +> **N.B:** Even though it is possible to also specify the language `per field`, our **default approach** is to translate all content at once based on the header, +> because it's so much simpler, and handles automated fallback, which is very useful if the content is not defined in the primary requested language. + +## Fetching translations through Locize provider + +> When the content we want to display doesn't come from GraphCMS API, then it's considered as a "static" content. +> +> This means that the content is managed by [Locize](https://www.locize.io/p/w7jrmdie/v/latest) and must be updated manually there. + +Check [this video](https://www.youtube.com/watch?v=9NOzJhgmyQE) to see Locize in action with react-i18next. + +### Locize translation workflow in-depth + +The Locize project contains two different **versions**: + +- `latest`: This **Locize version** is used in **any non-production application stage** (development, staging). That's where translations get added/updated by translators. +- `production`: This **Locize version** is only used in the production application stages (`APP_STAGE=production`) _(all customers share the same `production` version in the current setup, for the sake of simplicity)_ + +> This separation between the two versions is important and very useful to avoid deploying unapproved changes to the production stage. + +In order to update the `production` version, all changes must go through the `latest` version. +They can therefore be tested during the development phase, then during the staging phase. +Once you're ready to ship the content to production, the `production` version can be updated from the `latest` version, which will automatically update all customer production stages. + +> **Tip**: New i18n keys are added automatically in the `development` stage, as they are being added to the source code, thanks to the `saveMissing` [option](src/utils/i18nextLocize.ts). _This can also be a bit boring with HMR, because useless keys may be created while programming._ + +#### Locize additional services + +Locize provides a few [additional services](https://www.locize.io/p/w7jrmdie/services). Some are free, some are paid. + +#### Other additional services + +- One interesting thing is the ability to share part of the project to be translated by a third party using [`Crowdbased`](https://www.locize.io/p/w7jrmdie/services), without sharing the whole project. +- There are also several paid [Translation services](https://www.locize.io/p/w7jrmdie/services), where you can pay people to translate your content. +- It is also possible to enable [Audit](https://www.locize.io/p/w7jrmdie/services), which allows to audit every change to our translations, and keep changes up to 10 years. (_expensive_) + +--- + +# [GraphCMS](https://graphcms.com/?ref=unly-nrn) + +## Discount + +> Using the coupon code **`unly-nrn`** will grant you a 3-month 15% discount on the premium plans. + +## Fetching data from GraphCMS + +> We use multiple libraries to fetch data from GraphCMS. GraphCMS provides a GraphQL endpoint, so we use generic libraries to the GraphQL specification like `react-apollo`. +> +> [See full list of dependencies related to GraphCMS](README_DEPENDENCIES.md) + +There are several ways of fetching data from a GraphQL API: +- [`react-hoc`](https://www.apollographql.com/docs/react/api/react-hoc/): HOC (High Order Components) can be used with an components (classes, functional), the GraphQL query is described in the function's wrapper, outside of its body. + **Former way, tend to be deprecated in favor of `react-hooks` nowadays.** + [List of known issues](https://reactjs.org/docs/higher-order-components.html#caveats). +- [`Render Props`](https://reactjs.org/docs/render-props.html): Never used it, fixes some issues one can encounter with HOC, but hooks are still better. +- [**`react-hooks`**](https://www.apollographql.com/docs/react/api/react-hooks): Hooks can only be used with Functional components (not classes), the GraphQL query is described in the function's body. + +We used the hooks approach because it's just cleaner and simpler to understand. + +--- + +# [Amplitude](https://amplitude.com/) (Analytics) + +> Amplitude is used to collect usage metrics (analytics) of the application. + +Amplitude **is used only on the frontend part of the application**. It is composed of two parts: +- [`@amplitude/react-amplitude`](https://github.com/amplitude/react-amplitude): React components easy to use, see their [blog post](https://amplitude.engineering/introducing-react-amplitude-d7b5258bc708). +- [`amplitude-js`](https://github.com/amplitude/Amplitude-JavaScript): The JS SDK, only compatible from the browser. (They're working on making it [compatible with SSR](https://github.com/amplitude/Amplitude-JavaScript/issues/164)) + +See the [documentation example at react-amplitude](https://github.com/amplitude/react-amplitude#example-instrumenting-tic-tac-toe-from-facebooks-intro-to-react-tutorial) to understand how it's meant to be used. +We only use react-amplitude to manipulate events. + +> **Known limitation**: Amplitude doesn't provide any backend-compatible API. See https://github.com/amplitude/Amplitude-JavaScript/issues/164 + +## Chrome developer debug tool + +> The amplitude team has released a Chrome plugin to see the events from the browser. +> +> It is a **must-have** when working with Amplitude. It's very simple to use and quite helpful. + +- [Tutorial](https://help.amplitude.com/hc/en-us/articles/360003032451-Instrumentation-Explorer-Debugger) +- [Chrome plugin](https://chrome.google.com/webstore/detail/amplitude-instrumentation/acehfjhnmhbmgkedjmjlobpgdicnhkbp) + + +--- + +# Continuous Integration & Continuous Deployment (CI/CD) + +## Overview + +> Every time a commit is pushed to the repository, or a branch is merged, automated actions are triggered. +> +> Those actions are managed through Github Actions + +## Workflow of our Zeit <> Github Actions integration + +Here is how the multiple steps are ordered: + +1. [Event] A commit is pushed, a branch is merged (or on any change made on the remote repository) +1. [Trigger] Our [Github Actions](./.github/workflows) are triggered + - Either the staging scripts is executed, or the production script, depending on which branch is impacted (see [Github Actions <> Zeit integrations](./.github/workflows/README.md)) + - No matter what script (production vs staging) gets executed, those actions are always triggered: + 1. A new Zeit deployment is triggered, which runs our tests first (`yarn test:once`) (Failing tests will stop the deployment) + 1. Then, the deployment is deployed, and automatically linked to a custom domain which depends on the git branch name (xxx.now.sh) + 1. Then, our 2E2 tests are triggered using Cypress + - If they fail, artifacts (screenshots, videos) recorded by Cypress are uploaded to Github to help further debug + +## In-depth project's dependencies + +See [README_DEPENDENCIES](./README_DEPENDENCIES.md) + + +--- + +## License + +MIT + +--- + +# Vulnerability disclosure + +[See our policy](https://github.com/UnlyEd/Unly). + +--- + +# Contributors and maintainers + +This project is being maintained by: +- [Unly] Ambroise Dhenain ([Vadorequest](https://github.com/vadorequest)) **(active)** + +Special thanks to: +- [Contributor] Hugo Martin ([Demmonius](https://github.com/Demmonius)) - Github Actions CI/CD pipeline + +--- + +# **[ABOUT UNLY]** Unly logo + +> [Unly](https://unly.org) is a socially responsible company, fighting inequality and facilitating access to higher education. +> Unly is committed to making education more inclusive, through responsible funding for students. + +We provide technological solutions to help students find the necessary funding for their studies. + +We proudly participate in many TechForGood initiatives. To support and learn more about our actions to make education accessible, visit : +- https://twitter.com/UnlyEd +- https://www.facebook.com/UnlyEd/ +- https://www.linkedin.com/company/unly +- [Interested to work with us?](https://jobs.zenploy.io/unly/about) + +Tech tips and tricks from our CTO on our [Medium page](https://medium.com/unly-org/tech/home)! + +#TECHFORGOOD #EDUCATIONFORALL diff --git a/README_DEPENDENCIES.md b/README_DEPENDENCIES.md new file mode 100644 index 000000000..b52ba43b8 --- /dev/null +++ b/README_DEPENDENCIES.md @@ -0,0 +1,578 @@ +Unly logo + +Dependencies +=== + +> Documentation about the project's dependencies + + + +- [Dependencies](#dependencies) + * [Amplitude](#amplitude) + + [A note about Amplitude's pricing](#a-note-about-amplitudes-pricing) + * [Emotion](#emotion) + + [Note about `/** @jsx jsx */`](#note-about--jsx-jsx-) + * [FortAwesome/FontAwesome](#fortawesomefontawesome) + + [Note about FontAwesome usage](#note-about-fontawesome-usage) + * [Sentry](#sentry) + + [A note about Sentry usage](#a-note-about-sentry-usage) + * [Unly packages](#unly-packages) + * [Apollo with react](#apollo-with-react) + * [Bootstrap](#bootstrap) + + [Notes about Bootstrap/Reactstrap usage](#notes-about-bootstrapreactstrap-usage) + - [Notes about Reactstrap Tooltips and Modal (SSR-not-friendly)](#notes-about-reactstrap-tooltips-and-modal-ssr-not-friendly) + * [classnames](#classnames) + * [Cookies](#cookies) + + [Why not using `universal-cookie`?](#why-not-using-universal-cookie) + + [Cookies management abstraction `UniversalCookiesManager`](#cookies-management-abstraction-universalcookiesmanager) + * [css-to-react-native](#css-to-react-native) + * [deepmerge](#deepmerge) + * [GraphQL](#graphql) + * [I18n, i18next and Locize](#i18n-i18next-and-locize) + + [Language detection](#language-detection) + + [Translation](#translation) + * [isomorphic-unfetch](#isomorphic-unfetch) + * [json-stringify-safe](#json-stringify-safe) + * [Lodash](#lodash) + + [Note about Lodash TS typings](#note-about-lodash-ts-typings) + * [Next](#next) + * [rc-tooltip](#rc-tooltip) + * [React](#react) + * [recompose](#recompose) + * [webfontloader](#webfontloader) + * [winston](#winston) +- [Dev dependencies](#dev-dependencies) + * [@types](#types) + * [Now & Zeit](#now--zeit) + * [Debug WebStorm](#debug-webstorm) + * [Eslint](#eslint) + * [Tests](#tests) + + [Jest](#jest) + + [Cypress](#cypress) + * [Documentation](#documentation) + + + +--- + +# Dependencies + +> Overview of the project deps, why we use them, and (not always) quick explanations about them + +All the packages listed here are under open source, non-restrictive license (MIT, ISC, etc.) + +_The order follows the order in `package.json` (kinda alphabetical but not quite exactly `¯\_(ツ)_/¯`)_ + +## Amplitude + +> A JavaScript SDK for tracking events and revenue to Amplitude. + +When your want to perform Analytics in any app, people usually go for Google Analytics. We don't. +GA is too limited, and huge, with tons of useless stuff. We've used it, and we really don't recommend it for any SPA, especially when playing with universal app because it just sucks. + +> It's very hard to configure GA with universal apps, you'll end up with wrong analytics insights due to event multi triggers (SSR + CSR). It was built for another world. Not for SPA! + +Instead, we use (and recommend) Amplitude instead. The world of analytics is huge, and isn't cheap. + +- [`amplitude-js`](https://www.npmjs.com/package/amplitude-js): Top-level amplitude official lib, used by react-amplitude. +- [`@amplitude/react-amplitude`](https://www.npmjs.com/package/react-amplitude): React-friendly amplitude lib, non-officially maintained. Really useful when working with react. + +Amplitude allows to track events and users behaviour, who are two very different things, even if events are related to users. + +### A note about Amplitude's pricing + +Amplitude [offers a free plan](https://amplitude.com/pricing) that allows 10 million events per months (`$identity` events aren't counted towards `events`, they're free). + +Then, the cheapest plan is `Growth`, that **starts around $48k/year** (yup, it's a **huge** gap) + +If you want to benefit from the **Growth plan for free**, know that it's possible (but limited to 1 years), through their [startup Scholarship](https://amplitude.com/startups). +**They offer Scholarship for non-profit organisation too.** + +> But, a word of caution here, even if you benefit from the scholarship, make sure your business doesn't rely on Growth features when your Scholarship ends. +> +> They told us then always find a way to provide Amplitude at an acceptable price for non-profit + +Anyway, Amplitude is [one of the best out there for Analytics](https://stackshare.io/amplitude), if the free plan is enough for your needs, or if you can afford paid plans. +Also, their react integration is really good, even though it's not officially maintained and could use some love. + +--- + +## Emotion + +> Emotion is a library designed for writing css styles with JavaScript. [https://emotion.sh/docs/introduction](https://emotion.sh/docs/introduction) + +Next.js provides CSS-in-js using [`styled-jsx`](https://github.com/zeit/styled-jsx), but we dislike it for several reasons. +It's not very intuitive to write styles that way and it needs extra dependencies/configuration to work with nested components and such. + +Instead, we use [Emotion](https://emotion.sh/docs/introduction) in this project, +which allows to write components using either the `styled` notation, or the `css` notation. + +- [`@emotion/core`](https://emotion.sh/docs/css-prop): Necessary to use emotion, with built-in `css` notation support. +- [`@emotion/styled`](https://emotion.sh/docs/styled): Necessary to used the `styled` notation. +- [`emotion-theming`](https://www.npmjs.com/package/emotion-theming): Theming library inspired by styled-components + +It's strongly recommended to read the official documentation about how to use it. + +### Note about `/** @jsx jsx */` + +When using Emotion, the file must start with `/** @jsx jsx */` on top of it. +- [Explanation](https://stackoverflow.com/questions/53803466/what-does-the-comment-jsx-jsx-do-in-the-emotion-css-in-js-library) +- [Official doc](https://emotion.sh/docs/css-prop#jsx-pragma) + +> **TL;DR** _It basically tells the babel compiler to do something different and won't work if not specified._ + +--- + +## FortAwesome/FontAwesome + +[FontAwesome](https://github.com/FortAwesome/Font-Awesome) is an awesome icon toolkit. + +We use the pro (paid) version, but a free version is also available. + +- [`@fortawesome/fontawesome-svg-core`](https://www.npmjs.com/package/@fortawesome/fontawesome-svg-core): Necessary to use font-awesome with a node project. Contains libs to config the FA library. (see `src/pages/_app.tsx`) +- [`@fortawesome/pro-light-svg-icons`](https://www.npmjs.com/package/@fortawesome/pro-light-svg-icons): Contains the icons themselves, there is one package per free/pro and kind of icon (light, regular, solid), and specific packages per technology stack (vue, angular, react-native, etc.) +- [`@fortawesome/react-fontawesome`](https://www.npmjs.com/package/@fortawesome/react-fontawesome): Contains the icons themselves + +### Note about FontAwesome usage + +FontAwesome is a little trickier to use that we would like to. + +For any icon you want to use, you must first load it through the [`src/pages/_app.tsx`](src/pages/_app.tsx) file and then load it in the FA library, as follow: + +```typescript jsx +import { config, library } from '@fortawesome/fontawesome-svg-core'; +import { faGithub } from '@fortawesome/free-brands-svg-icons'; + +library.add(faGithub); +``` + +> This operation is required in order to make the FA icon load properly on the server side + +Then, you can use those icons in any react component: + +```typescript jsx +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +// Inside a react component + +``` + +--- + +## Sentry + +> Sentry provides open-source and hosted error monitoring that helps all software teams discover, triage, and prioritize errors in real-time. + +We use Sentry to catch errors that happen within the application. +They are available at [https://sentry.io/](https://sentry.io/) for any developers in the team. + +Those errors are also sent to our Slack channel `sentry-monitoring`. + +- [`@sentry/browser`](https://www.npmjs.com/package/@sentry/browser): Package to use from the browser. +- [`@sentry/node`](https://www.npmjs.com/package/@sentry/node): Package to use from the server. + +Sentry provides 2 different packages, with different abilities (but a very similar API) for browser and server usage. + +### A note about Sentry usage + +In the source code, we always use `@sentry/node`, which is automatically converted at build step by babel. (see [next.config.js](next.config.js)) + +This way, we always use the same import, which is linked to the right package based on the runtime engine target. + +--- + +## Unly packages + +We use some of our own packages. They are all hosted on ou [GitHub repository](https://github.com/UnlyEd), and all under MIT license. + +- [`@unly/utils`](https://github.com/UnlyEd/utils): This is a utility library which contains various helpers. This repository was created because those helpers are used amongst several projects and were copy/pasted, which is a bad practice. +- [`@unly/utils-simple-logger`](https://github.com/UnlyEd/utils-simple-logger): Logger based on [Winston](https://github.com/winstonjs/winston) with sane default so that it only logs `error` in production and filter other logs, while keeping them all in non-production environments. + Basically avoids to increase cost by logging useless logs in production. +- [`@unly/universal-language-detector`](https://github.com/UnlyEd/universal-language-detector): Language detector that works universally (browser + server) - Meant to be used with a universal framework, such as Next.js + +--- + +## [Apollo with react](https://github.com/apollographql/react-apollo) + +> React Apollo allows you to fetch data from your GraphQL server and use it in building complex and reactive UIs using the React framework. React Apollo may be used in any context that React may be used. + +- [`apollo-boost`](https://www.npmjs.com/package/apollo-boost): Apollo Boost is a zero-config way to start using Apollo Client. It includes some sensible defaults, such as our recommended InMemoryCache and HttpLink, which come configured for you with our recommended settings. + Even though it may seems unused, this package is required as peer-dependency. +- [`apollo-cache-inmemory`](https://www.npmjs.com/package/apollo-cache-inmemory): apollo-cache-inmemory is the recommended cache implementation for Apollo Client 2.0. InMemoryCache is a normalized data store that supports all of Apollo Client 1.0's features without the dependency on Redux. +- [`apollo-client`](https://www.npmjs.com/package/apollo-client): Apollo Client is a fully-featured caching GraphQL client with integrations for React, Angular, and more. It allows you to easily build UI components that fetch data via GraphQL. +- [`apollo-link-http`](https://www.npmjs.com/package/apollo-link-http): The http link is a terminating link that fetches GraphQL results from a GraphQL endpoint over an http connection. +- [`react-apollo`](https://www.npmjs.com/package/react-apollo): React Apollo allows you to fetch data from your GraphQL server and use it in building complex and reactive UIs using the React framework. React Apollo may be used in any context that React may be used. In the browser, in React Native, or in Node.js when you want to do server-side rendering. + +Our implementation is based on [this example](https://github.com/tomanagle/GraphQL-Apollo-Next.js) and uses the [`react hooks`](https://reactjs.org/docs/hooks-intro.html) recent feature. + +> It works fine with both SSR and client-side navigation. +> +> A universal [HOC](https://reactjs.org/docs/higher-order-components.html) is used to fetch data from our GraphQL endpoint: [withUniversalGraphQLDataLoader](src/hoc/withUniversalGraphQLDataLoader.ts). (both from browser and SSR) +> +> We don't use any authentication, as we connect to our GraphCMS Cache instance, which doesn't require any. +> +> We provide some headers on-the-fly (for I18n), that are added per-query basis. + +--- + +## [Bootstrap](https://getbootstrap.com/) & [Reactstrap](https://reactstrap.github.io/) + +> We use Reactstrap as a Components library, which is itself based on Bootstrap. + +- [`bootstrap`](https://www.npmjs.com/package/bootstrap): Necessary to load bootstrap.css file, which provides the styles +- [`reactstrap`](https://www.npmjs.com/package/reactstrap): +- [`@zeit/next-css`](https://github.com/zeit/next-plugins/tree/master/packages/next-css): [Additional Next configuration](https://stackoverflow.com/a/50002905/2391795) necessary to gain the ability to `import` `.css` files. + +### Notes about Bootstrap/Reactstrap usage + +> We are not quite satisfied with Reactstrap, to be honest. But we haven't found a better alternative so far (mostly because we're used to bootstrap, and lack of time for R&D). +> +> It does the job, but we dislike it more and more. + +Alternatives to Bootstrap/Reactstrap may be: +- [https://bulma.io/](https://bulma.io/) - 38k github stars +- [https://github.com/tailwindcss/tailwindcss](https://github.com/tailwindcss/tailwindcss) - 20k github stars + +Both look like promising CSS frameworks, there is likely more of them out there, but that's our top 2 in early 2020. + +#### Notes about Reactstrap Tooltips and Modal (SSR-not-friendly) + +We strongly suggest using another lib for Tooltips such as `rc-tooltip`. + +**Known issues**: +- [Using `Modal` or `Tooltip` with `isOpen={true}`](https://github.com/reactstrap/reactstrap/issues/1071) crashes the application completely, because SSR compilation fails due to missing `document`. + +--- + +## classnames + +> `classnames` is just the must-have tool to use to manipulate dynamic className property + +- [`classnames`](https://www.npmjs.com/package/classnames): A simple JavaScript utility for conditionally joining classNames together. + +--- + +## Cookies + +> It's hard (or at least, non-trivial) to make cookies work universally with Next.js + +- [`js-cookie`](https://github.com/js-cookie/js-cookie): Used to **WRITE** cookies from the **client** side. A simple, lightweight JavaScript API for handling browser cookies +- [`cookies`](https://www.npmjs.com/package/cookies): Used to **WRITE** cookies from the **server** side. Cookies is a node.js module for getting and setting HTTP(S) cookies. Cookies can be signed to prevent tampering, using Keygrip. It can be used with the built-in node.js HTTP library, or as Connect/Express middleware. + I'm not sure if that lib is the best choice, but it did work back then. Other alternative may be https://github.com/maticzav/nookies +- [`next-cookies`](https://www.npmjs.com/package/next-cookies): Used to **READ** cookies universally (cannot write). Tiny little function for getting cookies on both client & server with next.js. + This enables easy client-side and server-side rendering of pages that depend on cookies. + +A `cookies` prop is available to all **Page** and **Layout** components (through the `_app.tsx:render()`). + +### Why not using `universal-cookie`? + +[`universal-cookie`](https://github.com/reactivestack/cookies/tree/master/packages/universal-cookie) looks promising and if you know about it, +you may ask yourself why we don't use it instead of one package for the client side, and another one for the server side. + +Well, the answer is straightforward: **It does not work**. +See [https://github.com/reactivestack/cookies/issues/256](https://github.com/reactivestack/cookies/issues/256) + +### Cookies management abstraction `UniversalCookiesManager` + +We've built our own `UniversalCookiesManager` utility class to deal with cookies in a universal way (same API on client/server sides). + +It basically hides away the complexity, and allows for source code reusability. + +--- + +## css-to-react-native + +> Converts CSS text to a React Native stylesheet object. +> +> Converts all number-like values to numbers, and string-like to strings. +> +> Automatically converts indirect values to their React Native equivalents. + +- [`css-to-react-native`](https://www.npmjs.com/package/css-to-react-native): + +Used to dynamically convert CSS to React style object, for CSS rules coming from a data source (such as GraphCMS). + +Currently used by [GraphCMSAsset.tsx](src/components/GraphCMSAsset.tsx) + +--- + +## deepmerge + +> Merges the enumerable properties of two or more objects deeply. + +- [`deepmerge`](https://www.npmjs.com/package/deepmerge): + +Used in many places to merge different objects together. +Handles deeply nested objects. + +--- + +## GraphQL + +- [`graphql`](https://www.npmjs.com/package/graphql): Client for connecting to a GraphQL endpoint. +- [`graphql-tag`](https://www.npmjs.com/package/graphql-tag): Helpful utilities for parsing GraphQL queries. + Useful to write plain-text GraphQL query using the `gql` tag, that can be validated by other tools, such as **JS GraphQL IntelliJ Plugin**. +- [JS GraphQL IntelliJ Plugin](https://github.com/jimkyndemeyer/js-graphql-intellij-plugin): GraphQL language support for WebStorm, IntelliJ IDEA and other IDEs based on the IntelliJ Platform. + The plugin is available using WebStorm directly. To install it, open your IDE "Settings", "Plugins", "Marketplace" and search for "GraphQL". + But, it should be automatically made available by default. (through shared IDE config) + +The usage of both `gql` and the IntelliJ GraphQL plugin is awesome, it allows to write GraphQL queries (see [gql folder](./src/gql)) and have auto-completion and validation from WebStorm itself. + +To refresh the GraphQL spec, just run the `.graphqlconfig` file by opening it and run the stage you want to sync (usually staging). + +--- + +## I18n, i18next and Locize + +### Language detection +We rely on a few packages for I18n: +- [`accept-language-parser`](https://www.npmjs.com/package/accept-language-parser): Parses the accept-language header from an HTTP request and produces an array of language objects sorted by quality. + +Together, those packages are used in [i18n.ts](src/utils/i18n.ts) and are used to resolve the languages/codes used/preferred by the end user. +We do it manually instead of relying on another lib that would detect the language for us, because we need to be consistent with the language displayed to the end user, and need to provide the exact same value to GraphCMS and Locize. + +### Translation +We also rely on those packages to manage the translations: +- [`i18next-locize-backend`](https://www.npmjs.com/package/i18next-locize-backend): This is an i18next backend to be used for locize service. It will load resources from locize server using xhr. + It will allow you to save missing keys containing both default value and context information + **This backend is used when using the browser.** +- [`i18next-node-locize-backend`](https://www.npmjs.com/package/i18next-node-locize-backend): This is a i18next backend to be used with node.js for the locize service. It's for the node.js server what the i18next-locize-backend is for the browser. + **This backend is used when using node (server).** +- [`locize-editor`](https://www.npmjs.com/package/locize-editor): The locize-editor enables you to directly connect content from your website / application with your content on your localization project on locize. + Enabling the editor by querystring ?locize=true you can click any text content on your website to open it on the right side editor window + **This plugin is used when using the browser.** +- [`locize-node-lastused`](https://www.npmjs.com/package/locize-node-lastused): This is an i18next plugin or standalone scriot to be used for locize service. It will update last used timestamps on reference keys from your locize project using xhr. It sets the last used date on your reference language's namespaces. + **This plugin is used when using node (server).** + +It was quite complicated to configure Next with Locize, mostly because of the universal way Next works, while Locize has dedicated packages depending on the runtime engine. + +> See [i18nextLocize.ts](./src/utils/i18nextLocize.ts) to see how it was all put together. +> Also, we were inspired by [this SO question](https://stackoverflow.com/questions/55994799/how-to-integrate-next-i18next-nextjs-locize/58782594). + +--- + +## isomorphic-unfetch + +> Can be used to either polyfill the whole app, or used as a `fetch` function. _(Stands for "isomorphic universal fetch")_ + +- [`isomorphic-unfetch`](https://www.npmjs.com/package/isomorphic-unfetch): Switches between unfetch & node-fetch for client & server. + +There are several libs to allow fetching data from a react app, [here is a comparison](https://www.npmtrends.com/isomorphic-fetch-vs-isomorphic-unfetch-vs-universal-fetch). + +The main reason for choosing this one is its very small bundle size, and it's universal. + +--- + +## json-stringify-safe + +> Used to safely stringify JSON objects. Works even when they have circular dependencies + +- [`json-stringify-safe`](https://www.npmjs.com/package/json-stringify-safe): Like JSON.stringify, but doesn't throw on circular references. + +Use at your convenience. We weren't sure whether to use `json-stringify-safe` or `safe-json-stringify` and we made a wild choice here. + +--- + +## [Lodash](https://lodash.com/docs/4.17.15) + +> A modern JavaScript utility library delivering modularity, performance & extras. + +We made the choice to import lodash packages one-by-one instead of loading the whole `lodash` lib directly. +We're not sure if it's better/easier/wiser. We suppose it should decrease the bundle size, _but maybe it's natively handled by tree-shacking?_ + +- [`lodash.get`](https://www.npmjs.com/package/lodash.get): [https://lodash.com/docs/4.17.15#get](https://lodash.com/docs/4.17.15#get) +- [`lodash.isempty`](https://www.npmjs.com/package/lodash.isempty): [https://lodash.com/docs/4.17.15#isEmpty](https://lodash.com/docs/4.17.15#isEmpty) +- [`lodash.isplainobject`](https://www.npmjs.com/package/lodash.isplainobject): [https://lodash.com/docs/4.17.15#isPlainObject](https://lodash.com/docs/4.17.15#isPlainObject) +- [`lodash.map`](https://www.npmjs.com/package/lodash.map): [https://lodash.com/docs/4.17.15#map](https://lodash.com/docs/4.17.15#map) +... And tons of other + +We use plenty of utilities from lodash. Make sure read their [documentation](https://lodash.com/docs/). + +### Note about Lodash TS typings + +We also load each TS types one-by-one. One advantage of that is that **we can decide not to load typings that do not work**. + +> For instance, we tried using `@types/lodash.filter` but eventually removed it because it creates a mess that is hard to deal with. +> Typings may be wrong and breaks our tests, in such case it's nice to have the flexibility not to use them. + + + +--- + +## Next + +> Next.js framework package and plugins/utilities. + +- [`next`](https://www.npmjs.com/package/next): Next.js framework package. + See [tutorial](https://nextjs.org/learn/basics/getting-started). +- [`next-cookies`](https://www.npmjs.com/package/next-cookies): See [Cookies](#cookies) +- [`next-with-apollo`](https://www.npmjs.com/package/next-with-apollo): Apollo HOC for Next.js + + +--- + +## rc-tooltip + +> React Tooltip component + +- [`rc-tooltip`](https://www.npmjs.com/package/rc-tooltip): React tooltip + +Marked as alpha-3 version but stable. **Much better than Reactstrap Tooltip component.** + +--- + +## React + +> React package and plugins/utilities. + +- [`react`](https://www.npmjs.com/package/react): React is a JavaScript library for creating user interfaces. +- [`react-apollo`](https://www.npmjs.com/package/react-apollo): React Apollo allows you to fetch data from your GraphQL server and use it in building complex and reactive UIs using the React framework. React Apollo may be used in any context that React may be used. In the browser, in React Native, or in Node.js when you want to do server-side rendering. +- [`react-dom`](https://www.npmjs.com/package/react-dom): This package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as react to npm. +- [`react-style-proptype`](https://www.npmjs.com/package/react-style-proptype): Validates style objects by ensuring the keys are valid css property names (in camelcase form). +- [`prop-types`](https://www.npmjs.com/package/prop-types): Runtime type checking for React props and similar objects. + +> **N.B**: `react` and `react-dom` must always use the same version. + +--- + +## recompose + +> Used to compose multiple HOC together + +- [`recompose`](https://www.npmjs.com/package/recompose): Recompose is a React utility belt for function components and higher-order components. + +--- + +## webfontloader + +> Web Font Loader gives you added control when using linked fonts via @font-face. +> +> It provides a common interface to loading fonts regardless of the source, then adds a standard set of events you may use to control the loading experience. +> +> The Web Font Loader is able to load fonts from Google Fonts, Typekit, Fonts.com, and Fontdeck, as well as self-hosted web fonts. +> It is co-developed by Google and Typekit. + +- [`webfontloader`](https://www.npmjs.com/package/webfontloader): A logger for just about everything. + +--- + +## winston + +> Peer-dependency of `@unly/utils-simple-logger` + +- [`winston`](https://www.npmjs.com/package/winston): A logger for just about everything. + + +--- + +# Dev dependencies + +## @types + +> TypeScript requires Typings to resolve types. +> +> Those packages add additional types that allow TypeScript to resolve the related types, and allow for a better developer experience. +> Also, without some of those types, TS would fail to compile. +> +> Sometimes, TS types are included in the same package as the main package, sometimes in a different package, such as for those below. + +- [`@types/jest`](https://www.npmjs.com/package/@types/jest): This package contains type definitions for Jest (https://jestjs.io/). +- [`@types/react`](https://www.npmjs.com/package/@types/react): This package contains type definitions for React (http://facebook.github.io/react/). +- [`@types/webpack-env`](https://www.npmjs.com/package/@types/webpack-env): Allow to use [`__non_webpack_require__` with TypeScript](https://hackernoon.com/building-isomorphic-javascript-packages-1ba1c7e558c5). +- [`@typescript-eslint/eslint-plugin`](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin): ESLint plugin for TypeScript support .It is important that you use the same version number for `@typescript-eslint/parser` and `@typescript-eslint/eslint-plugin`. +- [`@typescript-eslint/parser`](https://www.npmjs.com/package/@typescript-eslint/parser): An ESLint custom parser which leverages TypeScript ESTree to allow for ESLint to lint TypeScript source code. +... lots more + +--- + +> Those dependencies are used only during the development and aren't shipped to the end-user. +> +> They are meant to make the development process easier/faster. + +## Now & Zeit + +> Official packages from Zeit/Now, the company hosting our application + +- [`@now/node`](https://zeit.co/docs/runtimes#official-runtimes/node-js): Used as a dev dependency, it's useful to provide [TypeScript support](https://zeit.co/docs/v2/serverless-functions/supported-languages/#node.js-typescript-support:). + Such as for `import { NowRequest, NowResponse } from '@now/node'`. +- [`now`](https://www.npmjs.com/package/now): The Now CLI [https://github.com/zeit/now](https://github.com/zeit/now) [https://www.npmjs.com/package/now](https://www.npmjs.com/package/now) +- [`@zeit/next-css`](https://github.com/zeit/next-plugins/tree/master/packages/next-css): [Additional Next configuration](https://stackoverflow.com/a/50002905/2391795) necessary to gain the ability to `import` `.css` files. + Used to import other libs such as `bootstrap.css`. +- [`@zeit/next-source-maps`](https://www.npmjs.com/package/@zeit/next-source-maps): Generate source maps during production build in your Next.js project + +## Debug WebStorm + +> Packages meant to help with the debug of the application + +- [`concurrently`](https://www.npmjs.com/package/concurrently): Run multiple commands concurrently. +- [`cross-env`](https://www.npmjs.com/package/cross-env): Run scripts that set and use environment variables across platforms. + +Together, those two packages are used by the WebStorm "Debug" configuration. (top right) + +Running the Debug configuration in `debug` mode allows to pause execution and use breakpoints. + +--- + +## Eslint + +> Eslint helps us enforce code style and check for typos and errors during the development process + +- [`eslint`](https://www.npmjs.com/package/eslint): ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. +- [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react): React specific linting rules for ESLint +- [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks): This ESLint plugin enforces the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html). It is a part of the Hooks API for React. +- [`eslint-plugin-jest`](https://www.npmjs.com/package/eslint-plugin-jest): ESLint plugin for Jest +- [`eslint-plugin-jsx-a11y`](https://www.npmjs.com/package/eslint-plugin-jsx-a11y): Static AST checker for accessibility rules on JSX elements. + +Eslint rules are automatically used by WebStorm. + +> Eslint with TypeScript and JSX support was configured [following this tutorial](http://www.thedreaming.org/2019/03/27/eslint-with-typescript/). + +Run `yarn lint` to run the linter. + +--- + +## Tests + +### [Jest](https://jestjs.io/) + +> Jest is our test runner, it runs our tests to make sure we don't ship regressions to our end users + +- [`jest`](https://www.npmjs.com/package/jest): Jest is a delightful JavaScript Testing Framework with a focus on simplicity. +- [`jest-extended`](https://www.npmjs.com/package/jest-extended): Additional Jest matchers. Provides additional built-in tests for ease of testing. +- [`react-test-renderer`](https://www.npmjs.com/package/react-test-renderer): This package provides an experimental React renderer that can be used to render React components to pure JavaScript objects, without depending on the DOM or a native mobile environment. + Essentially, this package makes it easy to grab a snapshot of the "DOM tree" rendered by a React DOM or React Native component without using a browser or jsdom. +- [`ts-jest`](https://www.npmjs.com/package/ts-jest): TypeScript preprocessor with source map support for Jest that lets you use Jest to test projects written in TypeScript. + +**Known issues**: +- `jest-emotion`: [Breaks tests](https://github.com/emotion-js/emotion/issues/1440#issuecomment-551235747) + +### Cypress + +> Cypress is a tool that helps performing end-to-end (E2E) tests that aim at testing the UI and the user workflows. +> +> It is open source and free to use from the command line (doesn't count as Tests recording). +> +> It also comes with a paid plan that provides a [Dashboard](https://dashboard.cypress.io/#/projects/4dvdog/runs), we use it with the Free plan, but it only allows [500 tests recordings per month](https://dashboard.cypress.io/#/organizations/1e9a77da-8ecf-4a84-8ad9-99a01ac2b098/billing). + +Several utility scripts have been configured to help with E2E testing, each script takes an optional `CYPRESS_STAGE` environment variable, which defines the [config file](./cypress/config-development.json) that will be used (`development` by default): +- `yarn e2e:open`: Runs the test suite **in a local browser** _(requires `yarn e2e:install`)_, targets **localhost development website**. (uses [cypress/config-development.json](./cypress/config-development.json)) +- `yarn e2e:run`: Runs the test suite **in a local console**, targets **localhost development website**. (uses [cypress/config-development.json](./cypress/config-development.json)) +- `CYPRESS_STAGE=customer1 yarn e2e:run` equivalent to `yarn e2e:customer1:production`: Runs the test suite in the console, targets **demo production website**. (uses [cypress/config-demo.json](cypress/config-customer2.json)) + +It is also possible to test all the apps at once: +- `yarn e2e:all:production`: This will run each production e2e test run (in series, parallel is not free) + +We used the following [**Cypress <> Next.js** tutorial](https://buttercms.com/blog/how-to-test-nextjs-apps) to get started. +Note that our current installation doesn't provide test coverage. +It's a bit harder to setup, here is a [tutorial](https://www.cypress.io/blog/2019/09/05/cypress-code-coverage-for-create-react-app-v3/) if ever needed. + +> **N.B**: [Here is the documentation about the options available in the config files](https://docs.cypress.io/guides/references/configuration.html). + +--- + +## Documentation + +- [`markdown-toc`](https://www.npmjs.com/package/markdown-toc): Generate a markdown TOC (table of contents). + _Uses many dependencies, many of them outdated (handlebars) and containing security issues, but we don't care much about those as they aren't shipped in the build, but only present on the developer's local machine._ +- [`version-bump-prompt`](https://www.npmjs.com/package/version-bump-prompt): Used to make it easier to bump versions. + diff --git a/README_HOW_TO_REMOVE.md b/README_HOW_TO_REMOVE.md new file mode 100644 index 000000000..98ee54796 --- /dev/null +++ b/README_HOW_TO_REMOVE.md @@ -0,0 +1,72 @@ +Unly logo + +How to remove "X" library/utility +=== + +> Explanation about how to remove any lib/utility you don't need/like + + + +- [Amplitude](#amplitude) +- [Emotion](#emotion) +- [Sentry](#sentry) +- [Locize & i18n](#locize--i18n) + + + +--- + +## Amplitude + +> You can use other alternative libraries for analytics. +> +> We've experimented with Google Analytics and were really disappointed by it. +> Amplitude is much better, both for configuring the events, documenting them and exploit them. But it's much more expensive. + +1. Remove the following libraries: + - [`amplitude-js`](https://www.npmjs.com/package/amplitude-js): Top-level amplitude official lib, used by react-amplitude. + - [`@amplitude/react-amplitude`](https://www.npmjs.com/package/react-amplitude): React-friendly amplitude lib, non-officially maintained. Really useful when working with react. + +1. Remove their components usage in the source code +1. Remove the `AMPLITUDE_API_KEY` env var + +--- + +## Emotion + +> We strongly recommend to keep Emotion. You can use both Styled Component approach and inline styles, it should feet all needs. + +1. Remove the following libraries: + - [`@emotion/core`](https://emotion.sh/docs/css-prop): Necessary to use emotion, with built-in `css` notation support. + - [`@emotion/styled`](https://emotion.sh/docs/styled): Necessary to used the `styled` notation. + - [`emotion-theming`](https://www.npmjs.com/package/emotion-theming): Theming library inspired by styled-components +1. Remove their components usage in the source code + `/** @jsx jsx */` + +--- + +## Sentry + +> You may replace Sentry by another monitoring tool of your choice. Make sure it is JS universal-friendly though. + +1. Remove the following libraries: + - [`@sentry/browser`](https://www.npmjs.com/package/@sentry/browser): Package to use from the browser. + - [`@sentry/node`](https://www.npmjs.com/package/@sentry/node): Package to use from the server. +1. Remove their components usage in the source code +1. Remove the `SENTRY_DSN` env var +1. Remove alias in [next.config.js](next.config.js) `config.resolve.alias['@sentry/node'] = '@sentry/browser';` + +--- + +## Locize & i18n + +> You may replace Locize by another internationalisation too of your choice. Make sure it is JS universal-friendly though. +> +> You may also completely remove i18n from your app, if you don't need it. (`i18next` and `react-i18next` packages) + +1. Remove the following libraries: +- [`i18next-locize-backend`](https://www.npmjs.com/package/i18next-locize-backend): This is an i18next backend to be used for locize service. It will load resources from locize server using xhr. +- [`i18next-node-locize-backend`](https://www.npmjs.com/package/i18next-node-locize-backend): This is a i18next backend to be used with node.js for the locize service. It's for the node.js server what the i18next-locize-backend is for the browser. +- [`locize-editor`](https://www.npmjs.com/package/locize-editor): The locize-editor enables you to directly connect content from your website / application with your content on your localization project on locize. +- [`locize-node-lastused`](https://www.npmjs.com/package/locize-node-lastused): This is an i18next plugin or standalone scriot to be used for locize service. It will update last used timestamps on reference keys from your locize project using xhr. It sets the last used date on your reference language's namespaces. +1. Remove their components usage in the source code +1. Remove the `LOCIZE_PROJECT_ID` and `LOCIZE_API_KEY` env var diff --git a/cypress/_examples/actions.spec.js b/cypress/_examples/actions.spec.js new file mode 100644 index 000000000..8923d99d3 --- /dev/null +++ b/cypress/_examples/actions.spec.js @@ -0,0 +1,281 @@ +/// + +context('Actions', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/actions') + }) + + // https://on.cypress.io/interacting-with-elements + + it('.type() - type into a DOM element', () => { + // https://on.cypress.io/type + cy.get('.action-email') + .type('fake@email.com').should('have.value', 'fake@email.com') + + // .type() with special character sequences + .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') + .type('{del}{selectall}{backspace}') + + // .type() with key modifiers + .type('{alt}{option}') //these are equivalent + .type('{ctrl}{control}') //these are equivalent + .type('{meta}{command}{cmd}') //these are equivalent + .type('{shift}') + + // Delay each keypress by 0.1 sec + .type('slow.typing@email.com', { delay: 100 }) + .should('have.value', 'slow.typing@email.com') + + cy.get('.action-disabled') + // Ignore error checking prior to type + // like whether the input is visible or disabled + .type('disabled error checking', { force: true }) + .should('have.value', 'disabled error checking') + }) + + it('.focus() - focus on a DOM element', () => { + // https://on.cypress.io/focus + cy.get('.action-focus').focus() + .should('have.class', 'focus') + .prev().should('have.attr', 'style', 'color: orange;') + }) + + it('.blur() - blur off a DOM element', () => { + // https://on.cypress.io/blur + cy.get('.action-blur').type('About to blur').blur() + .should('have.class', 'error') + .prev().should('have.attr', 'style', 'color: red;') + }) + + it('.clear() - clears an input or textarea element', () => { + // https://on.cypress.io/clear + cy.get('.action-clear').type('Clear this text') + .should('have.value', 'Clear this text') + .clear() + .should('have.value', '') + }) + + it('.submit() - submit a form', () => { + // https://on.cypress.io/submit + cy.get('.action-form') + .find('[type="text"]').type('HALFOFF') + cy.get('.action-form').submit() + .next().should('contain', 'Your form has been submitted!') + }) + + it('.click() - click on a DOM element', () => { + // https://on.cypress.io/click + cy.get('.action-btn').click() + + // You can click on 9 specific positions of an element: + // ----------------------------------- + // | topLeft top topRight | + // | | + // | | + // | | + // | left center right | + // | | + // | | + // | | + // | bottomLeft bottom bottomRight | + // ----------------------------------- + + // clicking in the center of the element is the default + cy.get('#action-canvas').click() + + cy.get('#action-canvas').click('topLeft') + cy.get('#action-canvas').click('top') + cy.get('#action-canvas').click('topRight') + cy.get('#action-canvas').click('left') + cy.get('#action-canvas').click('right') + cy.get('#action-canvas').click('bottomLeft') + cy.get('#action-canvas').click('bottom') + cy.get('#action-canvas').click('bottomRight') + + // .click() accepts an x and y coordinate + // that controls where the click occurs :) + + cy.get('#action-canvas') + .click(80, 75) // click 80px on x coord and 75px on y coord + .click(170, 75) + .click(80, 165) + .click(100, 185) + .click(125, 190) + .click(150, 185) + .click(170, 165) + + // click multiple elements by passing multiple: true + cy.get('.action-labels>.label').click({ multiple: true }) + + // Ignore error checking prior to clicking + cy.get('.action-opacity>.btn').click({ force: true }) + }) + + it('.dblclick() - double click on a DOM element', () => { + // https://on.cypress.io/dblclick + + // Our app has a listener on 'dblclick' event in our 'scripts.js' + // that hides the div and shows an input on double click + cy.get('.action-div').dblclick().should('not.be.visible') + cy.get('.action-input-hidden').should('be.visible') + }) + + it('.rightclick() - right click on a DOM element', () => { + // https://on.cypress.io/rightclick + + // Our app has a listener on 'contextmenu' event in our 'scripts.js' + // that hides the div and shows an input on right click + cy.get('.rightclick-action-div').rightclick().should('not.be.visible') + cy.get('.rightclick-action-input-hidden').should('be.visible') + }) + + it('.check() - check a checkbox or radio element', () => { + // https://on.cypress.io/check + + // By default, .check() will check all + // matching checkbox or radio elements in succession, one after another + cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') + .check().should('be.checked') + + cy.get('.action-radios [type="radio"]').not('[disabled]') + .check().should('be.checked') + + // .check() accepts a value argument + cy.get('.action-radios [type="radio"]') + .check('radio1').should('be.checked') + + // .check() accepts an array of values + cy.get('.action-multiple-checkboxes [type="checkbox"]') + .check(['checkbox1', 'checkbox2']).should('be.checked') + + // Ignore error checking prior to checking + cy.get('.action-checkboxes [disabled]') + .check({ force: true }).should('be.checked') + + cy.get('.action-radios [type="radio"]') + .check('radio3', { force: true }).should('be.checked') + }) + + it('.uncheck() - uncheck a checkbox element', () => { + // https://on.cypress.io/uncheck + + // By default, .uncheck() will uncheck all matching + // checkbox elements in succession, one after another + cy.get('.action-check [type="checkbox"]') + .not('[disabled]') + .uncheck().should('not.be.checked') + + // .uncheck() accepts a value argument + cy.get('.action-check [type="checkbox"]') + .check('checkbox1') + .uncheck('checkbox1').should('not.be.checked') + + // .uncheck() accepts an array of values + cy.get('.action-check [type="checkbox"]') + .check(['checkbox1', 'checkbox3']) + .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') + + // Ignore error checking prior to unchecking + cy.get('.action-check [disabled]') + .uncheck({ force: true }).should('not.be.checked') + }) + + it('.select() - select an option in a