Merge pull request #74 from Haiti-Distributed-Healthcare-System/update-linting-config

Attempt 2: Linting Config + Action
This commit is contained in:
Ben Mitchinson authored and GitHub committed 2020-04-19 13:10:14 -05:00
commit fc45bcfaee
29 files changed
+14763 -6153

No files matched your search

+1
View File
@@ -17,6 +17,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: docker-compose -f dev-docker-compose.yml up --detach
- run: docker exec haiti_pwa npm run lint
- run: docker exec haiti_pwa npm run test:cov
- run: docker exec haiti_pwa npm run cy:run
- run: docker exec haiti_pwa npm run build
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
extends: [
"plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
"plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
ecmaFeatures: {
jsx: true, // Allows for the parsing of JSX
},
},
rules: {
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/ban-ts-ignore": "off",
},
settings: {
react: {
version: "detect", // Tells eslint-plugin-react to automatically detect the version of React to use
},
},
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
semi: false,
trailingComma: 'all',
singleQuote: true,
printWidth: 80,
tabWidth: 4,
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
+31
View File
@@ -0,0 +1,31 @@
{
"eslint.autoFixOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
],
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.formatOnSave": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
+5 -5
View File
@@ -3,10 +3,10 @@
/// <reference types="cypress" />
describe('Homepage', () => {
it('shows login button', function () {
cy.visit('http://localhost:3000')
cy.get('.am-button').contains('Login')
})
it('shows login button', function () {
cy.visit('http://localhost:3000')
cy.get('.am-button').contains('Login')
})
})
export {};
export {}
+110 -48
View File
@@ -1840,42 +1840,42 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
},
"@typescript-eslint/eslint-plugin": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.0.tgz",
"integrity": "sha512-u7IcQ9qwsB6U806LupZmINRnQjC+RJyv36sV/ugaFWMHTbFm/hlLTRx3gGYJgHisxcGSTnf+I/fPDieRMhPSQQ==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz",
"integrity": "sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg==",
"requires": {
"@typescript-eslint/experimental-utils": "2.19.0",
"eslint-utils": "^1.4.3",
"@typescript-eslint/experimental-utils": "2.28.0",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/experimental-utils": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.0.tgz",
"integrity": "sha512-zwpg6zEOPbhB3+GaQfufzlMUOO6GXCNZq6skk+b2ZkZAIoBhVoanWK255BS1g5x9bMwHpLhX0Rpn5Fc3NdCZdg==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz",
"integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==",
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.19.0",
"eslint-scope": "^5.0.0"
"@typescript-eslint/typescript-estree": "2.28.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.19.0.tgz",
"integrity": "sha512-s0jZoxAWjHnuidbbN7aA+BFVXn4TCcxEVGPV8lWMxZglSs3NRnFFAlL+aIENNmzB2/1jUJuySi6GiM6uACPmpg==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.28.0.tgz",
"integrity": "sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw==",
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "2.19.0",
"@typescript-eslint/typescript-estree": "2.19.0",
"@typescript-eslint/experimental-utils": "2.28.0",
"@typescript-eslint/typescript-estree": "2.28.0",
"eslint-visitor-keys": "^1.1.0"
}
},
"@typescript-eslint/typescript-estree": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.0.tgz",
"integrity": "sha512-n6/Xa37k0jQdwpUszffi19AlNbVCR0sdvCs3DmSKMD7wBttKY31lhD2fug5kMD91B2qW4mQldaTEc1PEzvGu8w==",
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz",
"integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==",
"requires": {
"debug": "^4.1.1",
"eslint-visitor-keys": "^1.1.0",
@@ -2091,9 +2091,9 @@
}
},
"acorn": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ=="
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg=="
},
"acorn-globals": {
"version": "4.3.4",
@@ -2112,9 +2112,9 @@
}
},
"acorn-jsx": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz",
"integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw=="
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ=="
},
"acorn-walk": {
"version": "6.2.0",
@@ -5200,10 +5200,18 @@
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"globals": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz",
"integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==",
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"requires": {
"type-fest": "^0.8.1"
}
@@ -5229,6 +5237,23 @@
}
}
},
"eslint-config-prettier": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz",
"integrity": "sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==",
"dev": true,
"requires": {
"get-stdin": "^6.0.0"
},
"dependencies": {
"get-stdin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"dev": true
}
}
},
"eslint-config-react-app": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.0.tgz",
@@ -5506,6 +5531,15 @@
}
}
},
"eslint-plugin-prettier": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz",
"integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.0.tgz",
@@ -5547,9 +5581,9 @@
}
},
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
"integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
@@ -5560,12 +5594,12 @@
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A=="
},
"espree": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
"integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
"requires": {
"acorn": "^7.1.0",
"acorn-jsx": "^5.1.0",
"acorn": "^7.1.1",
"acorn-jsx": "^5.2.0",
"eslint-visitor-keys": "^1.1.0"
}
},
@@ -5575,11 +5609,18 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
"integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
"integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
"requires": {
"estraverse": "^4.0.0"
"estraverse": "^5.1.0"
},
"dependencies": {
"estraverse": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
"integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw=="
}
}
},
"esrecurse": {
@@ -5971,6 +6012,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
@@ -6189,9 +6236,9 @@
}
},
"flatted": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA=="
},
"flatten": {
"version": "1.0.3",
@@ -11753,6 +11800,21 @@
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"prettier": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz",
"integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-bytes": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",
@@ -12863,9 +12925,9 @@
}
},
"regexpp": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
"integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q=="
},
"regexpu-core": {
"version": "4.6.0",
@@ -14598,9 +14660,9 @@
}
},
"strip-json-comments": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w=="
},
"style-loader": {
"version": "1.1.3",
+72 -66
View File
@@ -1,69 +1,75 @@
{
"name": "hdhs",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.0.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.26",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"@types/react-router-dom": "^5.1.4",
"antd-mobile": "^2.3.1",
"node-sass": "^4.13.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.1",
"typescript": "^3.7.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"cy:open": "cypress open",
"cy:run": "cypress run",
"test:cov": "react-scripts test --coverage --watchAll=false",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"jest": {
"coverageThreshold": {
"global": {
"statements": 90
}
"name": "hdhs",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.0.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.26",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"@types/react-router-dom": "^5.1.4",
"antd-mobile": "^2.3.1",
"node-sass": "^4.13.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.1",
"typescript": "^3.7.5"
},
"coverageReporters": [
"lcov"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.3",
"@testing-library/react-hooks": "^3.2.1",
"awesome-typescript-loader": "^5.2.1",
"babel-loader": "^8.1.0",
"babel-plugin-import": "^1.13.0",
"cypress": "^4.2.0",
"react-test-renderer": "^16.13.1",
"svg-url-loader": "^5.0.0",
"ts-loader": "^6.2.2",
"webpack": "^4.42.1"
}
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"cy:open": "cypress open",
"cy:run": "cypress run",
"lint": "tsc --noEmit && eslint 'src/*.{js,ts,tsx}' --fix",
"test:cov": "react-scripts test --coverage --watchAll=false"
},
"eslintConfig": {
"extends": "react-app"
},
"jest": {
"coverageThreshold": {
"global": {
"statements": 90
}
},
"coverageReporters": [
"lcov"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.3",
"@testing-library/react-hooks": "^3.2.1",
"@typescript-eslint/eslint-plugin": "^2.28.0",
"@typescript-eslint/parser": "^2.28.0",
"awesome-typescript-loader": "^5.2.1",
"babel-loader": "^8.1.0",
"babel-plugin-import": "^1.13.0",
"cypress": "^4.2.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.1.3",
"prettier": "^2.0.4",
"react-test-renderer": "^16.13.1",
"svg-url-loader": "^5.0.0",
"ts-loader": "^6.2.2",
"webpack": "^4.42.1"
}
}
+7 -7
View File
@@ -1,9 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
import React from 'react'
import { render } from '@testing-library/react'
import App from './App'
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/Login/i);
expect(linkElement).toBeInTheDocument();
});
const { getByText } = render(<App />)
const linkElement = getByText(/Login/i)
expect(linkElement).toBeInTheDocument()
})
+35 -35
View File
@@ -1,39 +1,39 @@
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Login from "./components/Login";
import LandingPage from "./components/LandingPage";
import PatientInfoPlaceholder from "./components/PatientInfoPlaceholder";
import Wrapper from "./Wrapper";
import Login from './components/Login'
import LandingPage from './components/LandingPage'
import PatientInfoPlaceholder from './components/PatientInfoPlaceholder'
import Wrapper from './Wrapper'
import "./scss/App.scss";
import "./antd-mobile.css";
import './scss/App.scss'
import './antd-mobile.css'
const App = () => {
return (
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/landing">
<LandingPage />
</Route>
<Route path="/patientinfo">
<Wrapper
navTitle="Patient Information"
leftArrowText="Home"
leftArrowRoute="/"
>
<PatientInfoPlaceholder />
</Wrapper>
</Route>
<Route path="/">
<Login />
</Route>
</Switch>
</Router>
);
};
const App: React.FunctionComponent = () => {
return (
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/landing">
<LandingPage />
</Route>
<Route path="/patientinfo">
<Wrapper
navTitle="Patient Information"
leftArrowText="Home"
leftArrowRoute="/"
>
<PatientInfoPlaceholder />
</Wrapper>
</Route>
<Route path="/">
<Login />
</Route>
</Switch>
</Router>
)
}
export default App;
export default App
+60 -59
View File
@@ -1,74 +1,75 @@
import React from "react";
import { render, queryByAttribute, fireEvent } from "@testing-library/react";
import { useHistory } from "react-router-dom";
import Wrapper from "./Wrapper";
import React from 'react'
import { render, queryByAttribute, fireEvent } from '@testing-library/react'
import { useHistory } from 'react-router-dom'
import Wrapper from './Wrapper'
const mockHistoryPush = jest.fn();
jest.mock("react-router-dom", () => ({
useHistory: () => ({
push: mockHistoryPush,
}),
}));
const mockHistoryPush = jest.fn()
jest.mock('react-router-dom', () => ({
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
useHistory: () => ({
push: mockHistoryPush,
}),
}))
test("renders it's inner contents", () => {
const dom = render(
<Wrapper leftArrowText="back">
<div>Hello!</div>
</Wrapper>
);
const dom = render(
<Wrapper leftArrowText="back">
<div>Hello!</div>
</Wrapper>,
)
const textContents = dom.getByText("Hello!");
const textContents = dom.getByText('Hello!')
expect(textContents).toBeInTheDocument();
});
expect(textContents).toBeInTheDocument()
})
test("renders navBar when given navTitle", () => {
const dom = render(
<Wrapper navTitle="CHI">
<div>Hello!</div>
</Wrapper>
);
test('renders navBar when given navTitle', () => {
const dom = render(
<Wrapper navTitle="CHI">
<div>Hello!</div>
</Wrapper>,
)
const getById = queryByAttribute.bind(null, "id");
const renderedNavBar = getById(dom.container, "nav-bar");
const navTitle = dom.getByText("CHI");
const getById = queryByAttribute.bind(null, 'id')
const renderedNavBar = getById(dom.container, 'nav-bar')
const navTitle = dom.getByText('CHI')
expect(renderedNavBar).toBeTruthy();
expect(navTitle).toBeInTheDocument();
});
expect(renderedNavBar).toBeTruthy()
expect(navTitle).toBeInTheDocument()
})
test("renders no navBar when not given props", () => {
const dom = render(
<Wrapper>
<div>Hello!</div>
</Wrapper>
);
const getById = queryByAttribute.bind(null, "id");
const renderedNavBar = getById(dom.container, "nav-bar");
expect(renderedNavBar).toBeFalsy();
});
test('renders no navBar when not given props', () => {
const dom = render(
<Wrapper>
<div>Hello!</div>
</Wrapper>,
)
const getById = queryByAttribute.bind(null, 'id')
const renderedNavBar = getById(dom.container, 'nav-bar')
expect(renderedNavBar).toBeFalsy()
})
test("renders leftArrowText", () => {
const dom = render(
<Wrapper leftArrowText="back">
<div>Hello!</div>
</Wrapper>
);
test('renders leftArrowText', () => {
const dom = render(
<Wrapper leftArrowText="back">
<div>Hello!</div>
</Wrapper>,
)
const leftArrowText = dom.getByText("back");
const leftArrowText = dom.getByText('back')
expect(leftArrowText).toBeInTheDocument();
});
expect(leftArrowText).toBeInTheDocument()
})
test("redirects on left arrow click ", () => {
const dom = render(
<Wrapper leftArrowText="back" leftArrowRoute="/">
<div>Hello!</div>
</Wrapper>
);
test('redirects on left arrow click ', () => {
const dom = render(
<Wrapper leftArrowText="back" leftArrowRoute="/">
<div>Hello!</div>
</Wrapper>,
)
const leftArrowText = dom.getByText("back");
fireEvent.click(leftArrowText);
const leftArrowText = dom.getByText('back')
fireEvent.click(leftArrowText)
expect(useHistory().push).toBeCalledWith("/");
});
expect(useHistory().push).toBeCalledWith('/')
})
+39 -37
View File
@@ -1,42 +1,44 @@
import React from "react";
import { NavBar, Icon } from "antd-mobile";
import { useHistory } from "react-router-dom";
import React from 'react'
import { NavBar, Icon } from 'antd-mobile'
import { useHistory } from 'react-router-dom'
import "./scss/Wrapper.scss";
import './scss/Wrapper.scss'
type props = {
navTitle?: string;
leftArrowText?: string;
leftArrowRoute?: string;
children: React.ReactNode;
};
type WrapperProps = {
navTitle?: string
leftArrowText?: string
leftArrowRoute?: string
children: React.ReactNode
}
const Wrapper = (props: props) => {
const { navTitle, leftArrowText, leftArrowRoute } = props;
const hist = useHistory();
const needNavBar = navTitle || leftArrowText || leftArrowRoute;
const leftContentArr = leftArrowText
? [<Icon type="left" />, `${leftArrowText}`]
: [];
const leftRoute = leftArrowRoute ? `${leftArrowRoute}` : "/";
const Wrapper: React.FunctionComponent<WrapperProps> = (
props: WrapperProps,
) => {
const { navTitle, leftArrowText, leftArrowRoute } = props
const hist = useHistory()
const needNavBar = navTitle || leftArrowText || leftArrowRoute
const leftContentArr = leftArrowText
? [<Icon key={1} type="left" />, `${leftArrowText}`]
: []
const leftRoute = leftArrowRoute ? `${leftArrowRoute}` : '/'
return (
<div id="primary-wrapper">
<div id="wrapper-contents">
{needNavBar && (
<NavBar
id="nav-bar"
mode="light"
onLeftClick={() => hist.push(leftRoute)}
leftContent={leftContentArr}
>
{`${navTitle}`}
</NavBar>
)}
{props.children}
</div>
</div>
);
};
return (
<div id="primary-wrapper">
<div id="wrapper-contents">
{needNavBar && (
<NavBar
id="nav-bar"
mode="light"
onLeftClick={(): void => hist.push(leftRoute)}
leftContent={leftContentArr}
>
{`${navTitle}`}
</NavBar>
)}
{props.children}
</div>
</div>
)
}
export default Wrapper;
export default Wrapper
+5949 -5380
View File
File diff suppressed because it is too large. Load diff
+7875 -2
View File
File diff suppressed because it is too large. Load diff
+10 -10
View File
@@ -1,11 +1,11 @@
import React from "react";
import { render } from "@testing-library/react";
import LandingPage from "./LandingPage";
import React from 'react'
import { render } from '@testing-library/react'
import LandingPage from './LandingPage'
test("renders learn react link", () => {
const { getByText } = render(<LandingPage />);
const lookupButtonElement = getByText(/Patient Lookup/i);
const newPatientButtonElement = getByText(/New Patient/i);
expect(lookupButtonElement).toBeInTheDocument();
expect(newPatientButtonElement).toBeInTheDocument();
});
test('renders learn react link', () => {
const { getByText } = render(<LandingPage />)
const lookupButtonElement = getByText(/Patient Lookup/i)
const newPatientButtonElement = getByText(/New Patient/i)
expect(lookupButtonElement).toBeInTheDocument()
expect(newPatientButtonElement).toBeInTheDocument()
})
+30 -30
View File
@@ -1,35 +1,35 @@
import React, { ReactElement } from "react";
import { WhiteSpace, Button } from "antd-mobile";
import { UserAddOutlined } from "@ant-design/icons";
import React, { ReactElement } from 'react'
import { WhiteSpace, Button } from 'antd-mobile'
import { UserAddOutlined } from '@ant-design/icons'
import "../scss/LandingPage.scss";
import '../scss/LandingPage.scss'
export default function LandingPage(): ReactElement {
/* istanbul ignore next */
// TODO: these onClick functions could be mocked in a unit test
const patientLookup = () => {
console.log("Clicked Patient Lookup");
};
/* istanbul ignore next */
// TODO: these onClick functions could be mocked in a unit test
const patientLookup = () => {
console.log('Clicked Patient Lookup')
}
/* istanbul ignore next */
const newPatient = () => {
console.log("Clicked New Patient");
};
return (
<div id="landing-wrapper">
<div className="button-flex">
<Button onClick={patientLookup} icon="search">
Patient Lookup
</Button>
<WhiteSpace />
</div>
<br />
<div className="button-flex">
<Button icon={<UserAddOutlined />} onClick={newPatient}>
New Patient
</Button>
<WhiteSpace />
</div>
</div>
);
/* istanbul ignore next */
const newPatient = () => {
console.log('Clicked New Patient')
}
return (
<div id="landing-wrapper">
<div className="button-flex">
<Button onClick={patientLookup} icon="search">
Patient Lookup
</Button>
<WhiteSpace />
</div>
<br />
<div className="button-flex">
<Button icon={<UserAddOutlined />} onClick={newPatient}>
New Patient
</Button>
<WhiteSpace />
</div>
</div>
)
}
+24 -24
View File
@@ -1,29 +1,29 @@
import React, { ReactElement } from "react";
import useApi from "../hooks/useApi";
import { InputItem, Button } from "antd-mobile";
import "../scss/Login.scss";
import React, { ReactElement } from 'react'
import useApi from '../hooks/useApi'
import { InputItem, Button } from 'antd-mobile'
import '../scss/Login.scss'
export default function Login(): ReactElement {
const { response, isLoading } = useApi("");
const { response, isLoading } = useApi('')
return (
<>
<div id="loginWrapper">
<div id="loginGrid">
<div id="iconCard">
<div id="imgWrap">
<img src="/logoIcon512.jpg" alt="CHI Logo"></img>
return (
<>
<div id="loginWrapper">
<div id="loginGrid">
<div id="iconCard">
<div id="imgWrap">
<img src="/logoIcon512.jpg" alt="CHI Logo"></img>
</div>
</div>
<InputItem placeholder="username" />
<InputItem placeholder="password" type="password" />
<div id="buttonFlex">
<Button>Login</Button>
</div>
</div>
</div>
</div>
<InputItem placeholder="username" />
<InputItem placeholder="password" type="password" />
<div id="buttonFlex">
<Button>Login</Button>
</div>
</div>
</div>
<p>Loading API Fetch: {isLoading.toString()}</p>
{response && <p>API Fetch Result: {JSON.stringify(response)}</p>}
</>
);
<p>Loading API Fetch: {isLoading.toString()}</p>
{response && <p>API Fetch Result: {JSON.stringify(response)}</p>}
</>
)
}
@@ -1,12 +1,12 @@
/* istanbul ignore file */
import React, { ReactElement } from "react";
import React, { ReactElement } from 'react'
export default function PatientInfoPlaceholder(): ReactElement {
return (
<img
src="https://i.imgur.com/K5pR07w.png"
alt="placeholder"
style={{ width: "100%", height: "100%" }}
></img>
);
return (
<img
src="https://i.imgur.com/K5pR07w.png"
alt="placeholder"
style={{ width: '100%', height: '100%' }}
></img>
)
}
+20 -20
View File
@@ -1,28 +1,28 @@
import { renderHook } from "@testing-library/react-hooks";
import useApi from "./useApi";
import { renderHook } from '@testing-library/react-hooks'
import useApi from './useApi'
test("should return loading and then a response", async () => {
const { result, waitForNextUpdate } = renderHook(() => useApi(""));
test('should return loading and then a response', async () => {
const { result, waitForNextUpdate } = renderHook(() => useApi(''))
expect(result.current.response).toBeNull();
expect(result.current.error).toBeNull();
expect(result.current.isLoading).toBeTruthy();
expect(result.current.response).toBeNull()
expect(result.current.error).toBeNull()
expect(result.current.isLoading).toBeTruthy()
await waitForNextUpdate();
await waitForNextUpdate()
expect(result.current.response).toBeTruthy();
expect(result.current.error).toBeNull();
expect(result.current.isLoading).toBeFalsy();
});
expect(result.current.response).toBeTruthy()
expect(result.current.error).toBeNull()
expect(result.current.isLoading).toBeFalsy()
})
test("should return an error on a non existent route", async () => {
const { result, waitForNextUpdate } = renderHook(() => useApi("/404"));
test('should return an error on a non existent route', async () => {
const { result, waitForNextUpdate } = renderHook(() => useApi('/404'))
expect(result.current.response).toBeNull();
expect(result.current.error).toBeNull();
expect(result.current.response).toBeNull()
expect(result.current.error).toBeNull()
await waitForNextUpdate();
await waitForNextUpdate()
expect(result.current.response).toBeFalsy();
expect(result.current.error).toBeTruthy();
});
expect(result.current.response).toBeFalsy()
expect(result.current.error).toBeTruthy()
})
+29 -29
View File
@@ -1,35 +1,35 @@
import React from "react";
import React from 'react'
const useApi = (route: string) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
const [response, setResponse] = React.useState(null)
const [error, setError] = React.useState(null)
const [isLoading, setIsLoading] = React.useState(false)
/* istanbul ignore next */
const getUrl = () => {
if (process.env.environment === "docker") {
return "http://api:4000/";
} else {
return "http://localhost:4000/";
/* istanbul ignore next */
const getUrl = () => {
if (process.env.environment === 'docker') {
return 'http://api:4000/'
} else {
return 'http://localhost:4000/'
}
}
};
React.useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(getUrl() + route);
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return { response, error, isLoading };
};
React.useEffect(() => {
const fetchData = async () => {
setIsLoading(true)
try {
const res = await fetch(getUrl() + route)
const json = await res.json()
setResponse(json)
setIsLoading(false)
} catch (error) {
setError(error)
}
}
fetchData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return { response, error, isLoading }
}
export default useApi;
export default useApi
+7 -7
View File
@@ -1,13 +1,13 @@
/* istanbul ignore file */
import React from 'react';
import ReactDOM from 'react-dom';
import './scss/index.scss';
import App from './App';
import * as serviceWorker from './serviceWorker';
import React from 'react'
import ReactDOM from 'react-dom'
import './scss/index.scss'
import App from './App'
import * as serviceWorker from './serviceWorker'
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render(<App />, document.getElementById('root'))
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.register();
serviceWorker.register()
+2 -2
View File
@@ -1,7 +1,7 @@
.am-button:hover {
cursor: pointer;
cursor: pointer;
}
#root {
height: 100vh;
height: 100vh;
}
+13 -13
View File
@@ -1,19 +1,19 @@
.button-flex {
display: flex;
justify-content: center;
margin: 2em 0;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
display: flex;
justify-content: center;
margin: 2em 0;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
}
}
}
}
#landing-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}
+66 -66
View File
@@ -2,78 +2,78 @@
// @include media("<desktop") {
#loginWrapper {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
padding: 0 2rem;
#loginGrid {
display: grid;
align-items: center;
justify-content: center;
height: 45%;
#iconCard {
animation-name: fade-in-from-up;
animation-duration: 1.5s;
justify-content: center;
align-items: center;
flex-direction: row;
#imgWrap {
width: 28%;
margin: auto;
padding: 0.2rem;
background-color: #eee;
border-radius: 5px;
img {
display: block;
width: 100%;
max-width: 512px;
height: auto;
margin: auto;
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
padding: 0 2rem;
#loginGrid {
display: grid;
align-items: center;
justify-content: center;
height: 45%;
#iconCard {
animation-name: fade-in-from-up;
animation-duration: 1.5s;
justify-content: center;
align-items: center;
flex-direction: row;
#imgWrap {
width: 28%;
margin: auto;
padding: 0.2rem;
background-color: #eee;
border-radius: 5px;
img {
display: block;
width: 100%;
max-width: 512px;
height: auto;
margin: auto;
}
}
}
}
}
input {
animation-name: fade-in;
animation-duration: 2s;
}
#buttonFlex {
animation-name: fade-in;
animation-duration: 2s;
display: flex;
justify-content: center;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
input {
animation-name: fade-in;
animation-duration: 2s;
}
#buttonFlex {
animation-name: fade-in;
animation-duration: 2s;
display: flex;
justify-content: center;
.am-button {
background-color: #9bd7ff;
width: 40%;
&.am-button-active {
background-color: #ccebff;
}
}
}
}
}
}
$animationDelay: 1;
@for $i from 1 through 15 {
.loginGrid :nth-child(#{$i}) {
animation-delay: #{0.3+ ($i)/30}s;
$animationDelay: 1;
@for $i from 1 through 15 {
.loginGrid :nth-child(#{$i}) {
animation-delay: #{0.3+ ($i)/30}s;
}
}
}
@keyframes fade-in {
0% {
opacity: 0;
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
100% {
opacity: 1;
}
}
@keyframes fade-in-from-up {
0% {
opacity: 0;
transform: translateY(-100px);
@keyframes fade-in-from-up {
0% {
opacity: 0;
transform: translateY(-100px);
}
100% {
opacity: 1;
}
}
100% {
opacity: 1;
}
}
}
+10 -9
View File
@@ -1,11 +1,12 @@
#primary-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
#wrapper-contents {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
max-width: 50em;
max-height: 94.6vh;
}
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
#wrapper-contents {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.19);
max-width: 50em;
max-height: 94.6vh;
}
}
+200 -170
View File
@@ -22,7 +22,6 @@
/// @access public
////
///
/// Creates a list of global breakpoints
///
@@ -30,12 +29,11 @@
/// $breakpoints: ('phone': 320px);
///
$breakpoints: (
'phone': 320px,
'tablet': 768px,
'desktop': 1024px
'phone': 320px,
'tablet': 768px,
'desktop': 1024px,
) !default;
///
/// Creates a list of static expressions or media types
///
@@ -48,16 +46,17 @@ $breakpoints: (
/// );
///
$media-expressions: (
'screen': 'screen',
'print': 'print',
'handheld': 'handheld',
'landscape': '(orientation: landscape)',
'portrait': '(orientation: portrait)',
'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)',
'retina3x': '(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi), (min-resolution: 3dppx)'
'screen': 'screen',
'print': 'print',
'handheld': 'handheld',
'landscape': '(orientation: landscape)',
'portrait': '(orientation: portrait)',
'retina2x':
'(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)',
'retina3x':
'(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi), (min-resolution: 3dppx)',
) !default;
///
/// Defines a number to be added or subtracted from each unit when declaring breakpoints with exclusive intervals
///
@@ -80,10 +79,10 @@ $media-expressions: (
/// @media (min-width: 2.1rem) {}
///
$unit-intervals: (
'px': 1,
'em': 0.01,
'rem': 0.1,
'': 0
'px': 1,
'em': 0.01,
'rem': 0.1,
'': 0,
) !default;
///
@@ -175,7 +174,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access private
////
///
/// Log a message either with `@error` if supported
/// else with `@warn`, using `feature-exists('at-error')`
@@ -184,17 +182,16 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @param {String} $message - Message to log
///
@function im-log($message) {
@if feature-exists('at-error') {
@error $message;
} @else {
@warn $message;
$_: noop();
}
@if feature-exists('at-error') {
@error $message;
} @else {
@warn $message;
$_: noop();
}
@return $message;
@return $message;
}
///
/// Wrapper mixin for the log function so it can be used with a more friendly
/// API than `@if im-log('..') {}` or `$_: im-log('..')`. Basically, use the function
@@ -204,15 +201,16 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @param {String} $message - Message to log
///
@mixin log($message) {
@if im-log($message) {}
@if im-log($message) {
}
}
///
/// Function with no `@return` called next to `@warn` in Sass 3.3
/// to trigger a compiling error and stop the process.
///
@function noop() {}
@function noop() {
}
///
/// Determines whether a list of conditions is intercepted by the static breakpoint.
@@ -222,28 +220,30 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Boolean} - Returns true if the conditions are intercepted by the static breakpoint
///
@function im-intercepts-static-breakpoint($conditions...) {
$no-media-breakpoint-value: map-get($breakpoints, $im-no-media-breakpoint);
$no-media-breakpoint-value: map-get($breakpoints, $im-no-media-breakpoint);
@if not $no-media-breakpoint-value {
@if im-log('`#{$im-no-media-breakpoint}` is not a valid breakpoint.') {}
}
@each $condition in $conditions {
@if not map-has-key($media-expressions, $condition) {
$operator: get-expression-operator($condition);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($condition, $operator);
@if ($prefix == 'max' and $value <= $no-media-breakpoint-value) or
($prefix == 'min' and $value > $no-media-breakpoint-value) {
@return false;
}
} @else if not index($im-no-media-expressions, $condition) {
@return false;
@if not $no-media-breakpoint-value {
@if im-log('`#{$im-no-media-breakpoint}` is not a valid breakpoint.') {
}
}
}
@return true;
@each $condition in $conditions {
@if not map-has-key($media-expressions, $condition) {
$operator: get-expression-operator($condition);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($condition, $operator);
@if ($prefix == 'max' and $value <= $no-media-breakpoint-value) or
($prefix == 'min' and $value > $no-media-breakpoint-value)
{
@return false;
}
} @else if not index($im-no-media-expressions, $condition) {
@return false;
}
}
@return true;
}
////
@@ -252,7 +252,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access private
////
///
/// Get operator of an expression
///
@@ -261,21 +260,20 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - Any of `>=`, `>`, `<=`, `<`, `≥`, `≤`
///
@function get-expression-operator($expression) {
@each $operator in ('>=', '>', '<=', '<', '≥', '≤') {
@if str-index($expression, $operator) {
@return $operator;
@each $operator in ('>=', '>', '<=', '<', '≥', '≤') {
@if str-index($expression, $operator) {
@return $operator;
}
}
}
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('No operator found in `#{$expression}`.');
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('No operator found in `#{$expression}`.');
}
///
/// Get dimension of an expression, based on a found operator
///
@@ -285,18 +283,17 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - `width` or `height` (or potentially anything else)
///
@function get-expression-dimension($expression, $operator) {
$operator-index: str-index($expression, $operator);
$parsed-dimension: str-slice($expression, 0, $operator-index - 1);
$dimension: 'width';
$operator-index: str-index($expression, $operator);
$parsed-dimension: str-slice($expression, 0, $operator-index - 1);
$dimension: 'width';
@if str-length($parsed-dimension) > 0 {
$dimension: $parsed-dimension;
}
@if str-length($parsed-dimension) > 0 {
$dimension: $parsed-dimension;
}
@return $dimension;
@return $dimension;
}
///
/// Get dimension prefix based on an operator
///
@@ -305,10 +302,9 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - `min` or `max`
///
@function get-expression-prefix($operator) {
@return if(index(('<', '<=', '≤'), $operator), 'max', 'min');
@return if(index(('<', '<=', '≤'), $operator), 'max', 'min');
}
///
/// Get value of an expression, based on a found operator
///
@@ -318,36 +314,35 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Number} - A numeric value
///
@function get-expression-value($expression, $operator) {
$operator-index: str-index($expression, $operator);
$value: str-slice($expression, $operator-index + str-length($operator));
$operator-index: str-index($expression, $operator);
$value: str-slice($expression, $operator-index + str-length($operator));
@if map-has-key($breakpoints, $value) {
$value: map-get($breakpoints, $value);
} @else {
$value: to-number($value);
}
@if map-has-key($breakpoints, $value) {
$value: map-get($breakpoints, $value);
} @else {
$value: to-number($value);
}
$interval: map-get($unit-intervals, unit($value));
$interval: map-get($unit-intervals, unit($value));
@if not $interval {
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('Unknown unit `#{unit($value)}`.');
}
@if not $interval {
// It is not possible to include a mixin inside a function, so we have to
// rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
// functions cannot be called anywhere in Sass, we need to hack the call in
// a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
// Sass 3.3, change this line in `@if im-log(..) {}` instead.
$_: im-log('Unknown unit `#{unit($value)}`.');
}
@if $operator == '>' {
$value: $value + $interval;
} @else if $operator == '<' {
$value: $value - $interval;
}
@if $operator == '>' {
$value: $value + $interval;
} @else if $operator == '<' {
$value: $value - $interval;
}
@return $value;
@return $value;
}
///
/// Parse an expression to return a valid media-query expression
///
@@ -356,18 +351,18 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {String} - Valid media query
///
@function parse-expression($expression) {
// If it is part of $media-expressions, it has no operator
// then there is no need to go any further, just return the value
@if map-has-key($media-expressions, $expression) {
@return map-get($media-expressions, $expression);
}
// If it is part of $media-expressions, it has no operator
// then there is no need to go any further, just return the value
@if map-has-key($media-expressions, $expression) {
@return map-get($media-expressions, $expression);
}
$operator: get-expression-operator($expression);
$dimension: get-expression-dimension($expression, $operator);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($expression, $operator);
$operator: get-expression-operator($expression);
$dimension: get-expression-dimension($expression, $operator);
$prefix: get-expression-prefix($operator);
$value: get-expression-value($expression, $operator);
@return '(#{$prefix}-#{$dimension}: #{$value})';
@return '(#{$prefix}-#{$dimension}: #{$value})';
}
///
@@ -382,17 +377,17 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {List} Sliced list
///
@function slice($list, $start: 1, $end: length($list)) {
@if length($list) < 1 or $start > $end {
@return ();
}
@if length($list) < 1 or $start > $end {
@return ();
}
$result: ();
$result: ();
@for $i from $start through $end {
$result: append($result, nth($list, $i));
}
@for $i from $start through $end {
$result: append($result, nth($list, $i));
}
@return $result;
@return $result;
}
////
@@ -401,7 +396,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access private
////
///
/// Casts a string into a number
///
@@ -410,44 +404,57 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Number}
///
@function to-number($value) {
@if type-of($value) == 'number' {
@return $value;
} @else if type-of($value) != 'string' {
$_: im-log('Value for `to-number` should be a number or a string.');
}
$first-character: str-slice($value, 1, 1);
$result: 0;
$digits: 0;
$minus: ($first-character == '-');
$numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9);
// Remove +/- sign if present at first character
@if ($first-character == '+' or $first-character == '-') {
$value: str-slice($value, 2);
}
@for $i from 1 through str-length($value) {
$character: str-slice($value, $i, $i);
@if not (index(map-keys($numbers), $character) or $character == '.') {
@return to-length(if($minus, -$result, $result), str-slice($value, $i))
@if type-of($value) == 'number' {
@return $value;
} @else if type-of($value) != 'string' {
$_: im-log('Value for `to-number` should be a number or a string.');
}
@if $character == '.' {
$digits: 1;
} @else if $digits == 0 {
$result: $result * 10 + map-get($numbers, $character);
} @else {
$digits: $digits * 10;
$result: $result + map-get($numbers, $character) / $digits;
}
}
$first-character: str-slice($value, 1, 1);
$result: 0;
$digits: 0;
$minus: ($first-character == '-');
$numbers: (
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
);
@return if($minus, -$result, $result);
// Remove +/- sign if present at first character
@if ($first-character == '+' or $first-character == '-') {
$value: str-slice($value, 2);
}
@for $i from 1 through str-length($value) {
$character: str-slice($value, $i, $i);
@if not(index(map-keys($numbers), $character) or $character == '.') {
@return to-length(
if($minus, -$result, $result),
str-slice($value, $i)
);
}
@if $character == '.' {
$digits: 1;
} @else if $digits == 0 {
$result: $result * 10 + map-get($numbers, $character);
} @else {
$digits: $digits * 10;
$result: $result + map-get($numbers, $character) / $digits;
}
}
@return if($minus, -$result, $result);
}
///
/// Add `$unit` to `$value`
///
@@ -457,13 +464,29 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @return {Number} - `$value` expressed in `$unit`
///
@function to-length($value, $unit) {
$units: ('px': 1px, 'cm': 1cm, 'mm': 1mm, '%': 1%, 'ch': 1ch, 'pc': 1pc, 'in': 1in, 'em': 1em, 'rem': 1rem, 'pt': 1pt, 'ex': 1ex, 'vw': 1vw, 'vh': 1vh, 'vmin': 1vmin, 'vmax': 1vmax);
$units: (
'px': 1px,
'cm': 1cm,
'mm': 1mm,
'%': 1%,
'ch': 1ch,
'pc': 1pc,
'in': 1in,
'em': 1em,
'rem': 1rem,
'pt': 1pt,
'ex': 1ex,
'vw': 1vw,
'vh': 1vh,
'vmin': 1vmin,
'vmax': 1vmax,
);
@if not index(map-keys($units), $unit) {
$_: im-log('Invalid unit `#{$unit}`.');
}
@if not index(map-keys($units), $unit) {
$_: im-log('Invalid unit `#{$unit}`.');
}
@return $value * map-get($units, $unit);
@return $value * map-get($units, $unit);
}
///
@@ -505,19 +528,22 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// }
///
@mixin media-context($tweakpoints: (), $tweak-media-expressions: ()) {
// Save global configuration
$global-breakpoints: $breakpoints;
$global-media-expressions: $media-expressions;
// Save global configuration
$global-breakpoints: $breakpoints;
$global-media-expressions: $media-expressions;
// Update global configuration
$breakpoints: map-merge($breakpoints, $tweakpoints) !global;
$media-expressions: map-merge($media-expressions, $tweak-media-expressions) !global;
// Update global configuration
$breakpoints: map-merge($breakpoints, $tweakpoints) !global;
$media-expressions: map-merge(
$media-expressions,
$tweak-media-expressions
) !global;
@content;
@content;
// Restore global configuration
$breakpoints: $global-breakpoints !global;
$media-expressions: $global-media-expressions !global;
// Restore global configuration
$breakpoints: $global-breakpoints !global;
$media-expressions: $global-media-expressions !global;
}
////
@@ -526,7 +552,6 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @access public
////
///
/// Generates a media query based on a list of conditions
///
@@ -551,15 +576,20 @@ $im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;
/// @include media('>=350px', '<tablet', 'retina3x') { }
///
@mixin media($conditions...) {
@if ($im-media-support and length($conditions) == 0) or
(not $im-media-support and im-intercepts-static-breakpoint($conditions...)) {
@content;
} @else if ($im-media-support and length($conditions) > 0) {
@media #{unquote(parse-expression(nth($conditions, 1)))} {
// Recursive call
@include media(slice($conditions, 2)...) {
@if ($im-media-support and length($conditions) == 0) or
(
not
$im-media-support and
im-intercepts-static-breakpoint($conditions...)
)
{
@content;
}
} @else if ($im-media-support and length($conditions) > 0) {
@media #{unquote(parse-expression(nth($conditions, 1)))} {
// Recursive call
@include media(slice($conditions, 2) ...) {
@content;
}
}
}
}
}
+12 -12
View File
@@ -1,16 +1,16 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #e8ffd9;
height: 100%;
width: 100%;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #e8ffd9;
height: 100%;
width: 100%;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+112 -113
View File
@@ -1,4 +1,5 @@
/* istanbul ignore file */
/* eslint-disable */
// This optional code is used to register a service worker.
// register() is not called by default.
@@ -12,135 +13,133 @@
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
)
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
onSuccess?: (registration: ServiceWorkerRegistration) => void
onUpdate?: (registration: ServiceWorkerRegistration) => void
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA',
)
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config)
}
})
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
)
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration)
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration)
}
}
}
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
})
.catch((error) => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (
response.status === 404 ||
(contentType != null &&
contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.',
)
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister()
})
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import '@testing-library/jest-dom/extend-expect'