diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c9b21e5..bd68d57c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), #### Added * [#213](https://github.com/shlinkio/shlink-web-client/issues/213) The versions of both shlink-web-client and currently consumed Shlink server are now displayed in the footer. +* [#221](https://github.com/shlinkio/shlink-web-client/issues/221) Improved how servers are handled, displaying meaningful errors when a not-found or a not-reachable server is tried to be loaded. #### Changed diff --git a/package-lock.json b/package-lock.json index 4d5ca5d7..7945df60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2303,9 +2303,9 @@ }, "dependencies": { "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", "dev": true } } @@ -2536,24 +2536,181 @@ "dev": true }, "array.prototype.find": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", - "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.13.0" + "es-abstract": "^1.17.4" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } } }, "array.prototype.flat": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", - "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.10.0", - "function-bind": "^1.1.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } } }, "arrify": { @@ -5183,7 +5340,7 @@ }, "discontinuous-range": { "version": "1.0.0", - "resolved": "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", "dev": true }, @@ -5432,54 +5589,234 @@ "dev": true }, "enzyme": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz", - "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", "dev": true, "requires": { - "array.prototype.flat": "^1.2.1", - "cheerio": "^1.0.0-rc.2", - "function.prototype.name": "^1.1.0", + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", "has": "^1.0.3", - "html-element-map": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-callable": "^1.1.4", - "is-number-object": "^1.0.3", - "is-regex": "^1.0.4", - "is-string": "^1.0.4", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", "is-subset": "^0.1.1", "lodash.escape": "^4.0.1", "lodash.isequal": "^4.5.0", - "object-inspect": "^1.6.0", - "object-is": "^1.0.1", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", "object.assign": "^4.1.0", - "object.entries": "^1.0.4", - "object.values": "^1.0.4", - "raf": "^3.4.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.1.2" + "string.prototype.trim": "^1.2.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } } }, "enzyme-adapter-react-16": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz", - "integrity": "sha512-7PcOF7pb4hJUvjY7oAuPGpq3BmlCig3kxXGi2kFx0YzJHppqX1K8IIV9skT1IirxXlu8W7bneKi+oQ10QRnhcA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz", + "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==", "dev": true, "requires": { - "enzyme-adapter-utils": "^1.12.0", + "enzyme-adapter-utils": "^1.13.0", + "enzyme-shallow-equal": "^1.0.1", "has": "^1.0.3", "object.assign": "^4.1.0", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "prop-types": "^15.7.2", - "react-is": "^16.8.6", + "react-is": "^16.12.0", "react-test-renderer": "^16.0.0-0", "semver": "^5.7.0" }, "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", "dev": true }, "semver": { @@ -5487,21 +5824,156 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } } } }, "enzyme-adapter-utils": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz", - "integrity": "sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz", + "integrity": "sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==", "dev": true, "requires": { - "airbnb-prop-types": "^2.13.2", - "function.prototype.name": "^1.1.0", + "airbnb-prop-types": "^2.15.0", + "function.prototype.name": "^1.1.2", "object.assign": "^4.1.0", - "object.fromentries": "^2.0.0", + "object.fromentries": "^2.0.2", "prop-types": "^15.7.2", - "semver": "^5.6.0" + "semver": "^5.7.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } + } + }, + "enzyme-shallow-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", + "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object-is": "^1.0.2" + }, + "dependencies": { + "object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true + } } }, "errno": { @@ -7381,15 +7853,93 @@ "dev": true }, "function.prototype.name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.1.tgz", - "integrity": "sha512-e1NzkiJuw6xqVH7YSdiW/qDHebcmMhPNe6w+4ZYYEg0VA+LaLzx37RimbPLuonHhYGFGPx1ME2nSi74JiaCr/Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", + "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1", - "functions-have-names": "^1.1.1", - "is-callable": "^1.1.4" + "es-abstract": "^1.17.0-next.1", + "functions-have-names": "^1.2.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } } }, "functional-red-black-tree": { @@ -7399,9 +7949,9 @@ "dev": true }, "functions-have-names": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.1.1.tgz", - "integrity": "sha512-U0kNHUoxwPNPWOJaMG7Z00d4a/qZVrFtzWJRaK8V9goaVOCXBSQSJpt3MYGNtkScKEBKovxLjnNdC9MlXwo5Pw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", + "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==", "dev": true }, "gauge": { @@ -7935,9 +8485,9 @@ "dev": true }, "html-element-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.1.0.tgz", - "integrity": "sha512-iqiG3dTZmy+uUaTmHarTL+3/A2VW9ox/9uasKEZC+R/wAtUrTcRlXPSaPqsnWPfIu8wqn09jQNwMRqzL54jSYA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", + "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", "dev": true, "requires": { "array-filter": "^1.0.0" @@ -8510,9 +9060,9 @@ } }, "is-boolean-object": { - "version": "1.0.0", - "resolved": "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz", - "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", "dev": true }, "is-buffer": { @@ -8687,9 +9237,9 @@ } }, "is-number-object": { - "version": "1.0.3", - "resolved": "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz", - "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", "dev": true }, "is-obj": { @@ -8789,14 +9339,14 @@ "dev": true }, "is-string": { - "version": "1.0.4", - "resolved": "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz", - "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true }, "is-subset": { "version": "0.1.1", - "resolved": "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", "dev": true }, @@ -10007,13 +10557,13 @@ }, "lodash.escape": { "version": "4.0.1", - "resolved": "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", "dev": true }, "lodash.flattendeep": { "version": "4.4.0", - "resolved": "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, @@ -10025,7 +10575,7 @@ }, "lodash.isequal": { "version": "4.5.0", - "resolved": "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, @@ -10668,9 +11218,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moo": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", - "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", "dev": true }, "move-concurrently": { @@ -10806,22 +11356,22 @@ "dev": true }, "nearley": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.0.tgz", - "integrity": "sha512-2v52FTw7RPqieZr3Gth1luAXZR7Je6q3KaDHY5bjl/paDUdMu35fZ8ICNgiYJRr3tf3NMvIQQR1r27AvEr9CRA==", + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.1.tgz", + "integrity": "sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg==", "dev": true, "requires": { "commander": "^2.19.0", - "moo": "^0.4.3", + "moo": "^0.5.0", "railroad-diagrams": "^1.0.0", "randexp": "0.4.6", "semver": "^5.4.1" }, "dependencies": { "commander": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", - "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true } } @@ -13267,7 +13817,7 @@ }, "railroad-diagrams": { "version": "1.0.0", - "resolved": "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", "dev": true }, @@ -13730,22 +14280,32 @@ "integrity": "sha1-bjtFWV8tKV1GV78ZRJGYj5SMqr8=" }, "react-test-renderer": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.10.2.tgz", - "integrity": "sha512-k9Qzyev6cTIcIfrhgrFlYQAFxh5EEDO6ALNqYqmKsWVA7Q/rUMTay5nD3nthi6COmYsd4ghVYyi8U86aoeMqYQ==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.0.tgz", + "integrity": "sha512-NQ2S9gdMUa7rgPGpKGyMcwl1d6D9MCF0lftdI3kts6kkiX+qvpC955jNjAZXlIDTjnN9jwFI8A8XhRh/9v0spA==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", "react-is": "^16.8.6", - "scheduler": "^0.16.2" + "scheduler": "^0.19.0" }, "dependencies": { "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", "dev": true + }, + "scheduler": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.0.tgz", + "integrity": "sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } } } }, @@ -14417,7 +14977,7 @@ }, "rst-selector-parser": { "version": "2.2.3", - "resolved": "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", "dev": true, "requires": { @@ -15629,14 +16189,93 @@ } }, "string.prototype.trim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz", - "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", + "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.13.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } } }, "string.prototype.trimleft": { diff --git a/package.json b/package.json index 2f90e280..ce2b7b2f 100644 --- a/package.json +++ b/package.json @@ -85,8 +85,8 @@ "css-loader": "^3.2.0", "dotenv": "^8.1.0", "dotenv-expand": "^5.1.0", - "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.14.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", "eslint": "^5.11.1", "eslint-config-adidas-babel": "^1.1.0", "eslint-config-adidas-env": "^1.1.0", diff --git a/src/common/Home.js b/src/common/Home.js index e670e20e..7c7cf396 100644 --- a/src/common/Home.js +++ b/src/common/Home.js @@ -1,52 +1,35 @@ -import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { useEffect } from 'react'; import { isEmpty, values } from 'ramda'; -import React from 'react'; import { Link } from 'react-router-dom'; -import { ListGroup, ListGroupItem } from 'reactstrap'; import PropTypes from 'prop-types'; import './Home.scss'; +import ServersListGroup from '../servers/ServersListGroup'; -export default class Home extends React.Component { - static propTypes = { - resetSelectedServer: PropTypes.func, - servers: PropTypes.object, - }; +const propTypes = { + resetSelectedServer: PropTypes.func, + servers: PropTypes.object, +}; - componentDidMount() { - this.props.resetSelectedServer(); - } +const Home = ({ resetSelectedServer, servers: { list, loading } }) => { + const servers = values(list); + const hasServers = !isEmpty(servers); - render() { - const { servers: { list, loading } } = this.props; - const servers = values(list); - const hasServers = !isEmpty(servers); + useEffect(() => { + resetSelectedServer(); + }, []); - return ( -
-

Welcome to Shlink

-
- {!loading && hasServers && Please, select a server.} - {!loading && !hasServers && Please, add a server.} - {loading && Trying to load servers...} -
+ return ( +
+

Welcome to Shlink

+ + {!loading && hasServers && Please, select a server.} + {!loading && !hasServers && Please, add a server.} + {loading && Trying to load servers...} + +
+ ); +}; - {!loading && hasServers && ( - - {servers.map(({ name, id }) => ( - - {name} - - - ))} - - )} -
- ); - } -} +Home.propTypes = propTypes; + +export default Home; diff --git a/src/common/Home.scss b/src/common/Home.scss index a57f033b..ee360009 100644 --- a/src/common/Home.scss +++ b/src/common/Home.scss @@ -1,5 +1,4 @@ @import '../utils/base'; -@import '../utils/mixins/vertical-align'; .home { text-align: center; @@ -17,21 +16,3 @@ font-size: 2.2rem; } } - -.home__servers-list { - margin-top: 1rem; - width: 100%; - max-width: 400px; -} - -.home__servers-item.home__servers-item { - text-align: left; - position: relative; - padding: .75rem 2.5rem .75rem 1rem; -} - -.home__servers-item-icon { - @include vertical-align(); - - right: 1rem; -} diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js index 437f9d02..1951a63f 100644 --- a/src/common/MenuLayout.js +++ b/src/common/MenuLayout.js @@ -6,7 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import * as PropTypes from 'prop-types'; import { serverType } from '../servers/prop-types'; -import MutedMessage from '../utils/MutedMessage'; +import Message from '../utils/Message'; import NotFound from './NotFound'; import './MenuLayout.scss'; @@ -17,22 +17,28 @@ const propTypes = { selectedServer: serverType, }; -const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits, ShlinkVersions) => { +const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits, ShlinkVersions, ServerError) => { const MenuLayoutComp = ({ match, location, selectedServer, selectServer }) => { const [ showSideBar, setShowSidebar ] = useState(false); + const { params: { serverId } } = match; useEffect(() => { - const { params: { serverId } } = match; - selectServer(serverId); - }, []); + }, [ serverId ]); useEffect(() => setShowSidebar(false), [ location ]); if (!selectedServer) { - return ; + return ; + } + + if (selectedServer.serverNotFound) { + return ; + } + + if (selectedServer.serverNotReachable) { + return ; } - const { params: { serverId } } = match; const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': showSideBar, }); @@ -68,7 +74,7 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi } + render={() => List short URLs} /> diff --git a/src/common/NotFound.js b/src/common/NotFound.js index d34e0143..7a2355e9 100644 --- a/src/common/NotFound.js +++ b/src/common/NotFound.js @@ -4,17 +4,18 @@ import * as PropTypes from 'prop-types'; const propTypes = { to: PropTypes.string, - btnText: PropTypes.string, + children: PropTypes.node, }; -const NotFound = ({ to = '/', btnText = 'Home' }) => ( +const NotFound = ({ to = '/', children = 'Home' }) => (

Oops! We could not find requested route.

- Use your browser{'\''}s back button to navigate to the page you have previously come from, or just press this button. + Use your browser's back button to navigate to the page you have previously come from, or just press this + button.


- {btnText} + {children}
); diff --git a/src/common/services/provideServices.js b/src/common/services/provideServices.js index 2bde42b0..a8c842b6 100644 --- a/src/common/services/provideServices.js +++ b/src/common/services/provideServices.js @@ -27,7 +27,8 @@ const provideServices = (bottle, connect, withRouter) => { 'AsideMenu', 'CreateShortUrl', 'ShortUrlVisits', - 'ShlinkVersions' + 'ShlinkVersions', + 'ServerError' ); bottle.decorator('MenuLayout', connect([ 'selectedServer', 'shortUrlsListParams' ], [ 'selectServer' ])); bottle.decorator('MenuLayout', withRouter); diff --git a/src/servers/ServersDropdown.js b/src/servers/ServersDropdown.js index 20583787..c66cb22c 100644 --- a/src/servers/ServersDropdown.js +++ b/src/servers/ServersDropdown.js @@ -8,7 +8,6 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React static propTypes = { servers: PropTypes.object, selectedServer: serverType, - selectServer: PropTypes.func, listServers: PropTypes.func, history: PropTypes.shape({ push: PropTypes.func, @@ -16,14 +15,10 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React }; renderServers = () => { - const { servers: { list, loading }, selectedServer, selectServer } = this.props; + const { servers: { list, loading }, selectedServer } = this.props; const servers = values(list); const { push } = this.props.history; - const loadServer = (id) => { - selectServer(id) - .then(() => push(`/server/${id}/list-short-urls/1`)) - .catch(() => {}); - }; + const loadServer = (id) => push(`/server/${id}/list-short-urls/1`); if (loading) { return Trying to load servers...; @@ -41,10 +36,7 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React ))} - serversExporter.exportServers()} - > + serversExporter.exportServers()}> Export servers diff --git a/src/servers/ServersListGroup.js b/src/servers/ServersListGroup.js new file mode 100644 index 00000000..476922c1 --- /dev/null +++ b/src/servers/ServersListGroup.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ListGroup, ListGroupItem } from 'reactstrap'; +import { Link } from 'react-router-dom'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons'; +import { serverType } from './prop-types'; +import './ServersListGroup.scss'; + +const propTypes = { + servers: PropTypes.arrayOf(serverType).isRequired, + children: PropTypes.node.isRequired, +}; + +const ServerListItem = ({ id, name }) => ( + + {name} + + +); + +ServerListItem.propTypes = { + id: PropTypes.string, + name: PropTypes.string, +}; + +const ServersListGroup = ({ servers, children }) => ( + +
{children}
+ {servers.length > 0 && ( + + {servers.map(({ id, name }) => )} + + )} +
+); + +ServersListGroup.propTypes = propTypes; + +export default ServersListGroup; diff --git a/src/servers/ServersListGroup.scss b/src/servers/ServersListGroup.scss new file mode 100644 index 00000000..9f869468 --- /dev/null +++ b/src/servers/ServersListGroup.scss @@ -0,0 +1,18 @@ +@import '../utils/mixins/vertical-align'; + +.servers-list__list-group { + width: 100%; + max-width: 400px; +} + +.servers-list__server-item.servers-list__server-item { + text-align: left; + position: relative; + padding: .75rem 2.5rem .75rem 1rem; +} + +.servers-list__server-item-icon { + @include vertical-align(); + + right: 1rem; +} diff --git a/src/servers/helpers/ServerError.js b/src/servers/helpers/ServerError.js new file mode 100644 index 00000000..96f1d449 --- /dev/null +++ b/src/servers/helpers/ServerError.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import Message from '../../utils/Message'; +import ServersListGroup from '../ServersListGroup'; +import './ServerError.scss'; + +const propTypes = { + servers: PropTypes.object, + type: PropTypes.oneOf([ 'not-found', 'not-reachable' ]).isRequired, +}; + +export const ServerError = ({ type, servers: { list } }) => ( +
+
+ + {type === 'not-found' && 'Could not find this Shlink server.'} + {type === 'not-reachable' && ( + +

Oops! Could not connect to this Shlink server.

+ Make sure you have internet connection, and the server is properly configured and on-line. +
+ )} +
+
+ + These are the {type === 'not-reachable' ? 'other' : ''} Shlink servers currently configured. Choose one of + them or add a new one. + +
+); + +ServerError.propTypes = propTypes; diff --git a/src/servers/helpers/ServerError.scss b/src/servers/helpers/ServerError.scss new file mode 100644 index 00000000..073758ae --- /dev/null +++ b/src/servers/helpers/ServerError.scss @@ -0,0 +1,6 @@ +.server-error-container { + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/servers/prop-types/index.js b/src/servers/prop-types/index.js index caec6f92..4bdb7355 100644 --- a/src/servers/prop-types/index.js +++ b/src/servers/prop-types/index.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -export const serverType = PropTypes.shape({ +const regularServerType = PropTypes.shape({ id: PropTypes.string, name: PropTypes.string, url: PropTypes.string, @@ -8,3 +8,17 @@ export const serverType = PropTypes.shape({ version: PropTypes.string, printableVersion: PropTypes.string, }); + +const notFoundServerType = PropTypes.shape({ + serverNotFound: PropTypes.bool.isRequired, +}); + +const notReachableServerType = PropTypes.shape({ + serverNotReachable: PropTypes.bool.isRequired, +}); + +export const serverType = PropTypes.oneOfType([ + regularServerType, + notFoundServerType, + notReachableServerType, +]); diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js index 861fba85..9d646458 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.js @@ -21,20 +21,37 @@ const versionToSemVer = pipe( export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serverId) => async (dispatch) => { + dispatch(resetSelectedServer()); dispatch(resetShortUrlParams()); - const selectedServer = findServerById(serverId); - const { health } = buildShlinkApiClient(selectedServer); - const { version } = await health().catch(() => MIN_FALLBACK_VERSION); - dispatch({ - type: SELECT_SERVER, - selectedServer: { - ...selectedServer, - version: versionToSemVer(version), - printableVersion: versionToPrintable(version), - }, - }); + if (!selectedServer) { + dispatch({ + type: SELECT_SERVER, + selectedServer: { serverNotFound: true }, + }); + + return; + } + + try { + const { health } = buildShlinkApiClient(selectedServer); + const { version } = await health(); + + dispatch({ + type: SELECT_SERVER, + selectedServer: { + ...selectedServer, + version: versionToSemVer(version), + printableVersion: versionToPrintable(version), + }, + }); + } catch (e) { + dispatch({ + type: SELECT_SERVER, + selectedServer: { serverNotReachable: true }, + }); + } }; export default handleActions({ diff --git a/src/servers/services/provideServices.js b/src/servers/services/provideServices.js index e4231e87..9991d15b 100644 --- a/src/servers/services/provideServices.js +++ b/src/servers/services/provideServices.js @@ -7,6 +7,7 @@ import ImportServersBtn from '../helpers/ImportServersBtn'; import { resetSelectedServer, selectServer } from '../reducers/selectedServer'; import { createServer, createServers, deleteServer, listServers } from '../reducers/server'; import ForServerVersion from '../helpers/ForServerVersion'; +import { ServerError } from '../helpers/ServerError'; import ServersImporter from './ServersImporter'; import ServersService from './ServersService'; import ServersExporter from './ServersExporter'; @@ -18,7 +19,7 @@ const provideServices = (bottle, connect, withRouter) => { bottle.serviceFactory('ServersDropdown', ServersDropdown, 'ServersExporter'); bottle.decorator('ServersDropdown', withRouter); - bottle.decorator('ServersDropdown', connect([ 'servers', 'selectedServer' ], [ 'listServers', 'selectServer' ])); + bottle.decorator('ServersDropdown', connect([ 'servers', 'selectedServer' ], [ 'listServers' ])); bottle.serviceFactory('DeleteServerModal', () => DeleteServerModal); bottle.decorator('DeleteServerModal', withRouter); @@ -32,6 +33,9 @@ const provideServices = (bottle, connect, withRouter) => { bottle.serviceFactory('ForServerVersion', () => ForServerVersion); bottle.decorator('ForServerVersion', connect([ 'selectedServer' ])); + bottle.serviceFactory('ServerError', () => ServerError); + bottle.decorator('ServerError', connect([ 'servers' ])); + // Services bottle.constant('csvjson', csvjson); bottle.service('ServersImporter', ServersImporter, 'csvjson'); diff --git a/src/short-urls/ShortUrls.js b/src/short-urls/ShortUrls.js index 3efe9868..e9aa96d6 100644 --- a/src/short-urls/ShortUrls.js +++ b/src/short-urls/ShortUrls.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import Paginator from './Paginator'; @@ -14,9 +14,13 @@ const ShortUrls = (SearchBar, ShortUrlsList) => { const { match: { params }, shortUrlsList } = props; const { page, serverId } = params; const { data = [], pagination } = shortUrlsList; + const [ urlsListKey, setUrlsListKey ] = useState(`${serverId}_${page}`); // Using a key on a component makes react to create a new instance every time the key changes - const urlsListKey = `${serverId}_${page}`; + // Without it, pagination on the URL will not make the component to be refreshed + useEffect(() => { + setUrlsListKey(`${serverId}_${page}`); + }, [ serverId, page ]); return ( diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js index 94fb0b10..94d63894 100644 --- a/src/short-urls/ShortUrlsList.js +++ b/src/short-urls/ShortUrlsList.js @@ -112,9 +112,9 @@ const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Compon return shortUrlsList.map((shortUrl) => ( diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js index 9cee6953..914a1ba8 100644 --- a/src/tags/TagsList.js +++ b/src/tags/TagsList.js @@ -1,7 +1,7 @@ import React from 'react'; import { splitEvery } from 'ramda'; import PropTypes from 'prop-types'; -import MutedMessage from '../utils/MutedMessage'; +import Message from '../utils/Message'; import SearchField from '../utils/SearchField'; const { ceil } = Math; @@ -29,7 +29,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component { const { tagsList, match } = this.props; if (tagsList.loading) { - return ; + return ; } if (tagsList.error) { @@ -43,7 +43,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component { const tagsCount = tagsList.filteredTags.length; if (tagsCount < 1) { - return No tags found; + return No tags found; } const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); diff --git a/src/utils/MutedMessage.js b/src/utils/Message.js similarity index 53% rename from src/utils/MutedMessage.js rename to src/utils/Message.js index 61e5c90c..67b8c087 100644 --- a/src/utils/MutedMessage.js +++ b/src/utils/Message.js @@ -5,21 +5,35 @@ import PropTypes from 'prop-types'; import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +const getClassForType = (type) => { + const map = { + error: 'border-danger', + }; + + return map[type] || ''; +}; +const getTextClassForType = (type) => { + const map = { + error: 'text-danger', + }; + + return map[type] || 'text-muted'; +}; + const propTypes = { noMargin: PropTypes.bool, loading: PropTypes.bool, children: PropTypes.node, + type: PropTypes.oneOf([ 'default', 'error' ]), }; -const MutedMessage = ({ children, loading = false, noMargin = false }) => { - const cardClasses = classNames('bg-light', { - 'mt-4': !noMargin, - }); +const Message = ({ children, loading = false, noMargin = false, type = 'default' }) => { + const cardClasses = classNames('bg-light', getClassForType(type), { 'mt-4': !noMargin }); return (
-

+

{loading && } {loading && !children && Loading...} {children} @@ -29,6 +43,6 @@ const MutedMessage = ({ children, loading = false, noMargin = false }) => { ); }; -MutedMessage.propTypes = propTypes; +Message.propTypes = propTypes; -export default MutedMessage; +export default Message; diff --git a/src/utils/mixins/vertical-align.scss b/src/utils/mixins/vertical-align.scss index 5af5038c..95b5985c 100644 --- a/src/utils/mixins/vertical-align.scss +++ b/src/utils/mixins/vertical-align.scss @@ -1,4 +1,4 @@ -@mixin vertical-align($extraTransforms: '') { +@mixin vertical-align($extraTransforms: null) { position: absolute; top: 50%; transform: translateY(-50%) $extraTransforms; diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index a0875b0b..7330cc12 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -4,7 +4,7 @@ import { Card } from 'reactstrap'; import PropTypes from 'prop-types'; import qs from 'qs'; import DateRangeRow from '../utils/DateRangeRow'; -import MutedMessage from '../utils/MutedMessage'; +import Message from '../utils/Message'; import { formatDate } from '../utils/utils'; import SortableBarGraph from './SortableBarGraph'; import { shortUrlVisitsType } from './reducers/shortUrlVisits'; @@ -64,7 +64,7 @@ const ShortUrlVisits = ( if (loading) { const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...'; - return {message}; + return {message}; } if (error) { @@ -76,7 +76,7 @@ const ShortUrlVisits = ( } if (isEmpty(visits)) { - return There are no visits matching current filter :(; + return There are no visits matching current filter :(; } const { os, browsers, referrers, countries, cities, citiesForMap } = processStatsFromVisits( diff --git a/test/common/Home.test.js b/test/common/Home.test.js index 249b3e5d..fa6e7993 100644 --- a/test/common/Home.test.js +++ b/test/common/Home.test.js @@ -1,12 +1,11 @@ import { shallow } from 'enzyme'; -import { values } from 'ramda'; import React from 'react'; import Home from '../../src/common/Home'; describe('', () => { let wrapped; const defaultProps = { - resetSelectedServer: () => '', + resetSelectedServer: jest.fn(), servers: { loading: false, list: {} }, }; const createComponent = (props) => { @@ -17,26 +16,12 @@ describe('', () => { return wrapped; }; - afterEach(() => { - if (wrapped !== undefined) { - wrapped.unmount(); - wrapped = undefined; - } - }); - - it('resets selected server when mounted', () => { - const resetSelectedServer = jest.fn(); - - expect(resetSelectedServer).not.toHaveBeenCalled(); - createComponent({ resetSelectedServer }); - expect(resetSelectedServer).toHaveBeenCalled(); - }); + afterEach(() => wrapped && wrapped.unmount()); it('shows link to create server when no servers exist', () => { const wrapped = createComponent(); expect(wrapped.find('Link')).toHaveLength(1); - expect(wrapped.find('ListGroup')).toHaveLength(0); }); it('shows message when loading servers', () => { @@ -45,21 +30,17 @@ describe('', () => { expect(span).toHaveLength(1); expect(span.text()).toContain('Trying to load servers...'); - expect(wrapped.find('ListGroup')).toHaveLength(0); }); - it('shows servers list when list of servers is not empty', () => { - const servers = { - loading: false, - list: { - 1: { name: 'foo', id: '123' }, - 2: { name: 'bar', id: '456' }, - }, - }; - const wrapped = createComponent({ servers }); + it('Asks to select a server when not loadign and servers exist', () => { + const list = [ + { name: 'foo', id: '1' }, + { name: 'bar', id: '2' }, + ]; + const wrapped = createComponent({ servers: { list } }); + const span = wrapped.find('span'); - expect(wrapped.find('Link')).toHaveLength(0); - expect(wrapped.find('ListGroup')).toHaveLength(1); - expect(wrapped.find('ListGroupItem')).toHaveLength(values(servers).length); + expect(span).toHaveLength(1); + expect(span.text()).toContain('Please, select a server.'); }); }); diff --git a/test/common/NotFound.test.js b/test/common/NotFound.test.js index a230cc56..00d3739b 100644 --- a/test/common/NotFound.test.js +++ b/test/common/NotFound.test.js @@ -38,7 +38,7 @@ describe('', () => { }); it('shows a link with provided props', () => { - const { wrapper } = createWrapper({ to: '/foo/bar', btnText: 'Hello' }); + const { wrapper } = createWrapper({ to: '/foo/bar', children: 'Hello' }); const link = wrapper.find(Link); expect(link.prop('to')).toEqual('/foo/bar'); diff --git a/test/servers/ServersListGroup.test.js b/test/servers/ServersListGroup.test.js new file mode 100644 index 00000000..b2871bbe --- /dev/null +++ b/test/servers/ServersListGroup.test.js @@ -0,0 +1,34 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import { ListGroup } from 'reactstrap'; +import ServersListGroup from '../../src/servers/ServersListGroup'; + +describe('', () => { + let wrapped; + const createComponent = (servers) => { + wrapped = shallow(The list of servers); + + return wrapped; + }; + + afterEach(() => wrapped && wrapped.unmount()); + + it('Renders title', () => { + const wrapped = createComponent([]); + const title = wrapped.find('h5'); + + expect(title).toHaveLength(1); + expect(title.text()).toEqual('The list of servers'); + }); + + it('shows servers list', () => { + const servers = [ + { name: 'foo', id: '123' }, + { name: 'bar', id: '456' }, + ]; + const wrapped = createComponent(servers); + + expect(wrapped.find(ListGroup)).toHaveLength(1); + expect(wrapped.find('ServerListItem')).toHaveLength(servers.length); + }); +}); diff --git a/test/servers/helpers/ServerError.test.js b/test/servers/helpers/ServerError.test.js new file mode 100644 index 00000000..1e9d7417 --- /dev/null +++ b/test/servers/helpers/ServerError.test.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { BrowserRouter } from 'react-router-dom'; +import { ServerError } from '../../../src/servers/helpers/ServerError'; + +describe('', () => { + let wrapper; + + afterEach(() => wrapper && wrapper.unmount()); + + it.each([ + [ + 'not-found', + [ 'Could not find this Shlink server.' ], + 'These are the Shlink servers', + ], + [ + 'not-reachable', + [ + 'Oops! Could not connect to this Shlink server.', + 'Make sure you have internet connection, and the server is properly configured and on-line.', + ], + 'These are the other Shlink servers', + ], + ])('renders expected information based on type', (type, expectedTitleParts, expectedBody) => { + wrapper = shallow(); + const wrapperText = wrapper.html(); + const textsToFind = [ ...expectedTitleParts, ...expectedBody ]; + + expect.assertions(textsToFind.length); + textsToFind.forEach((text) => { + expect(wrapperText).toContain(text); + }); + }); +}); diff --git a/test/servers/reducers/selectedServer.test.js b/test/servers/reducers/selectedServer.test.js index 33728d9f..4cecf8cd 100644 --- a/test/servers/reducers/selectedServer.test.js +++ b/test/servers/reducers/selectedServer.test.js @@ -58,9 +58,10 @@ describe('selectedServerReducer', () => { await selectServer(ServersServiceMock, buildApiClient)(serverId)(dispatch); - expect(dispatch).toHaveBeenCalledTimes(2); - expect(dispatch).toHaveBeenNthCalledWith(1, { type: RESET_SHORT_URL_PARAMS }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + expect(dispatch).toHaveBeenCalledTimes(3); + expect(dispatch).toHaveBeenNthCalledWith(1, { type: RESET_SELECTED_SERVER }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: RESET_SHORT_URL_PARAMS }); + expect(dispatch).toHaveBeenNthCalledWith(3, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); }); it('invokes dependencies', async () => { @@ -70,17 +71,27 @@ describe('selectedServerReducer', () => { expect(buildApiClient).toHaveBeenCalledTimes(1); }); - it('falls back to min version when health endpoint fails', async () => { - const expectedSelectedServer = { - ...selectedServer, - version: MIN_FALLBACK_VERSION, - }; + it('dispatches error when health endpoint fails', async () => { + const expectedSelectedServer = { serverNotReachable: true }; apiClientMock.health.mockRejectedValue({}); await selectServer(ServersServiceMock, buildApiClient)(serverId)(dispatch); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + expect(apiClientMock.health).toHaveBeenCalled(); + expect(dispatch).toHaveBeenNthCalledWith(3, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + }); + + it('dispatches error when server is not found', async () => { + const expectedSelectedServer = { serverNotFound: true }; + + ServersServiceMock.findServerById.mockReturnValue(undefined); + + await selectServer(ServersServiceMock, buildApiClient)(serverId)(dispatch); + + expect(ServersServiceMock.findServerById).toHaveBeenCalled(); + expect(apiClientMock.health).not.toHaveBeenCalled(); + expect(dispatch).toHaveBeenNthCalledWith(3, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); }); }); }); diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.js index 5a2ce549..bba25539 100644 --- a/test/tags/TagsList.test.js +++ b/test/tags/TagsList.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { identity } from 'ramda'; import createTagsList from '../../src/tags/TagsList'; -import MutedMessage from '../../src/utils/MutedMessage'; +import Message from '../../src/utils/Message'; import SearchField from '../../src/utils/SearchField'; import { rangeOf } from '../../src/utils/utils'; @@ -28,7 +28,7 @@ describe('', () => { it('shows a loading message when tags are being loaded', () => { const wrapper = createWrapper({ loading: true }); - const loadingMsg = wrapper.find(MutedMessage); + const loadingMsg = wrapper.find(Message); expect(loadingMsg).toHaveLength(1); expect(loadingMsg.html()).toContain('Loading...'); @@ -44,7 +44,7 @@ describe('', () => { it('shows a message when the list of tags is empty', () => { const wrapper = createWrapper({ filteredTags: [] }); - const msg = wrapper.find(MutedMessage); + const msg = wrapper.find(Message); expect(msg).toHaveLength(1); expect(msg.html()).toContain('No tags found'); diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js index 004019cf..785f216a 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.js @@ -3,7 +3,7 @@ import { shallow } from 'enzyme'; import { identity } from 'ramda'; import { Card } from 'reactstrap'; import createShortUrlVisits from '../../src/visits/ShortUrlVisits'; -import MutedMessage from '../../src/utils/MutedMessage'; +import Message from '../../src/utils/Message'; import GraphCard from '../../src/visits/GraphCard'; import SortableBarGraph from '../../src/visits/SortableBarGraph'; import DateRangeRow from '../../src/utils/DateRangeRow'; @@ -44,7 +44,7 @@ describe('', () => { it('renders a preloader when visits are loading', () => { const wrapper = createComponent({ loading: true }); - const loadingMessage = wrapper.find(MutedMessage); + const loadingMessage = wrapper.find(Message); expect(loadingMessage).toHaveLength(1); expect(loadingMessage.html()).toContain('Loading...'); @@ -52,7 +52,7 @@ describe('', () => { it('renders a warning when loading large amounts of visits', () => { const wrapper = createComponent({ loading: true, loadingLarge: true }); - const loadingMessage = wrapper.find(MutedMessage); + const loadingMessage = wrapper.find(Message); expect(loadingMessage).toHaveLength(1); expect(loadingMessage.html()).toContain('This is going to take a while... :S'); @@ -68,7 +68,7 @@ describe('', () => { it('renders a message when visits are loaded but the list is empty', () => { const wrapper = createComponent({ loading: false, error: false, visits: [] }); - const message = wrapper.find(MutedMessage); + const message = wrapper.find(Message); expect(message).toHaveLength(1); expect(message.html()).toContain('There are no visits matching current filter :(');