mirror of
https://github.com/gosticks/web.git
synced 2026-10-06 22:57:01 +00:00
Merge branch 'development' into isdampe_ftl_style
This commit is contained in:
@@ -8,7 +8,7 @@ version: 2
|
||||
.test: &test
|
||||
run:
|
||||
name: "Test"
|
||||
command: npm test
|
||||
command: npm run coverage -- --maxWorkers=4
|
||||
|
||||
.store_version: &store_version
|
||||
run:
|
||||
@@ -50,6 +50,11 @@ jobs:
|
||||
chmod +x ./FTL-client
|
||||
./FTL-client "$FOLDER" pihole-web.tar.gz "$FTL_SECRET"
|
||||
rm ./FTL-client
|
||||
- run:
|
||||
name: "Upload Code Coverage"
|
||||
command: |
|
||||
npm install codecov -g
|
||||
codecov
|
||||
- save_cache:
|
||||
key: v1-build-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
|
||||
@@ -9,6 +9,9 @@ node_modules
|
||||
build
|
||||
src/scss/style.css
|
||||
|
||||
# Test Files
|
||||
coverage
|
||||
|
||||
# Generated Files
|
||||
src/languages.json
|
||||
|
||||
|
||||
Generated
+61
-42
@@ -1177,9 +1177,9 @@
|
||||
}
|
||||
},
|
||||
"@coreui/coreui": {
|
||||
"version": "2.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@coreui/coreui/-/coreui-2.1.11.tgz",
|
||||
"integrity": "sha512-qGgRcNstB6bqW+D5UMUFQHPVUD4gfTmsPsZpiQYNLvAKWCszq2LhilTIWAAJHxs9yf2iTer0s30MjccTsqjXOA==",
|
||||
"version": "2.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@coreui/coreui/-/coreui-2.1.12.tgz",
|
||||
"integrity": "sha512-wO2olnHWyvzW6CHaiU4oRq/tomuuHItiifcyU40UBN00vACjBUH4kpxfhZGGjwM031GRZRL6LnAWzwZb9wg8Lw==",
|
||||
"requires": {
|
||||
"@coreui/coreui-plugin-npm-postinstall": "^1.0.2",
|
||||
"bootstrap": "^4.3.1",
|
||||
@@ -1300,9 +1300,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@fortawesome/fontawesome-free": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.8.2.tgz",
|
||||
"integrity": "sha512-E4fDUF4fbu9AxKpaQQqCN3XBnNzb/5e0Gvd9OaQsYkK574LVI57v/EqqPfIm/mC7jYbxaPNrhvT5AF+Yzwyizg=="
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.9.0.tgz",
|
||||
"integrity": "sha512-g795BBEzM/Hq2SYNPm/NQTIp3IWd4eXSH0ds87Na2jnrAUFX3wkyZAI4Gwj9DOaWMuz2/01i8oWI7P7T/XLkhg=="
|
||||
},
|
||||
"@hapi/address": {
|
||||
"version": "2.0.0",
|
||||
@@ -2610,9 +2610,9 @@
|
||||
}
|
||||
},
|
||||
"@types/jest": {
|
||||
"version": "24.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.13.tgz",
|
||||
"integrity": "sha512-3m6RPnO35r7Dg+uMLj1+xfZaOgIHHHut61djNjzwExXN4/Pm9has9C6I1KMYSfz7mahDhWUOVg4HW/nZdv5Pww==",
|
||||
"version": "24.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.14.tgz",
|
||||
"integrity": "sha512-IxS2AO0nOr4zrpKfRCxobQUb1bSK6ejodZ7odCzHXMjsASCI8J10N8qVQhrCjvJTc3bUjGGeuD+ytKZqyhajqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/jest-diff": "*"
|
||||
@@ -2648,10 +2648,19 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash.isequal": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz",
|
||||
"integrity": "sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "12.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz",
|
||||
"integrity": "sha512-j8YL2C0fXq7IONwl/Ud5Kt0PeXw22zGERt+HSSnwbKOJVsAGkEz3sFCYwaF9IOuoG1HOtE0vKCj6sXF7Q0+Vaw==",
|
||||
"version": "12.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz",
|
||||
"integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
@@ -9990,13 +9999,6 @@
|
||||
"integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "16.8.6",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
|
||||
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
@@ -10517,9 +10519,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"i18next": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-17.0.1.tgz",
|
||||
"integrity": "sha512-pg+RxJWoqEt4JtueOkoxXshivtmsf8u0SuTYQTSB5cUTNWUyw/Py3zF//A/vd5FEUaiooyPCNNJ94leQ8J4JEQ==",
|
||||
"version": "17.0.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-17.0.3.tgz",
|
||||
"integrity": "sha512-vQyW6a4ZLt3Dxnd6GXSnhbW5DwGYC4uLPKk1MFE5pfFbR9CEiNatdwwUZDQfrcNOh2x0eOGDFYeCEyLlkLvDQA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1"
|
||||
},
|
||||
@@ -14892,8 +14894,7 @@
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
|
||||
"dev": true
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
|
||||
},
|
||||
"lodash.isfunction": {
|
||||
"version": "3.0.9",
|
||||
@@ -15250,6 +15251,31 @@
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
|
||||
"dev": true
|
||||
},
|
||||
"mini-create-react-context": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz",
|
||||
"integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.0",
|
||||
"gud": "^1.0.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
|
||||
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
|
||||
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"mini-css-extract-plugin": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz",
|
||||
@@ -17875,9 +17901,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.1.tgz",
|
||||
"integrity": "sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==",
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
|
||||
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-bytes": {
|
||||
@@ -18719,39 +18745,32 @@
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.0.tgz",
|
||||
"integrity": "sha512-6EQDakGdLG/it2x9EaCt9ZpEEPxnd0OCLBHQ1AcITAAx7nCnyvnzf76jKWG1s2/oJ7SSviUgfWHofdYljFexsA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz",
|
||||
"integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"create-react-context": "^0.2.2",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"mini-create-react-context": "^0.3.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "16.8.6",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
|
||||
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.0.tgz",
|
||||
"integrity": "sha512-wSpja5g9kh5dIteZT3tUoggjnsa+TPFHSMrpHXMpFsaHhQkm/JNVGh2jiF9Dkh4+duj4MKCkwO6H08u6inZYgQ==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz",
|
||||
"integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.0.0",
|
||||
"react-router": "5.0.1",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
}
|
||||
|
||||
+10
-7
@@ -8,9 +8,10 @@
|
||||
"@types/enzyme": "^3.9.3",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.5",
|
||||
"@types/fetch-mock": "^7.3.0",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/jest": "^24.0.14",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/node": "^12.0.4",
|
||||
"@types/lodash.isequal": "^4.5.5",
|
||||
"@types/node": "^12.0.8",
|
||||
"@types/react": "^16.8.19",
|
||||
"@types/react-bootstrap-daterangepicker": "0.0.26",
|
||||
"@types/react-bootstrap-typeahead": "^3.4.5",
|
||||
@@ -30,24 +31,25 @@
|
||||
"node-fetch": "^2.6.0",
|
||||
"node-sass-chokidar": "^1.3.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^1.17.1",
|
||||
"prettier": "^1.18.2",
|
||||
"react-scripts": "^3.0.1",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"typescript": "^3.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coreui/coreui": "^2.1.11",
|
||||
"@coreui/coreui": "^2.1.12",
|
||||
"@coreui/icons": "0.3.0",
|
||||
"@coreui/react": "^2.5.1",
|
||||
"@fortawesome/fontawesome-free": "^5.8.2",
|
||||
"@fortawesome/fontawesome-free": "^5.9.0",
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-daterangepicker": "^3.0.5",
|
||||
"chart.js": "^2.8.0",
|
||||
"i18next": "^17.0.1",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"i18next-xhr-backend": "^3.0.0",
|
||||
"ionicons": "^4.5.8",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"moment": "^2.24.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.6",
|
||||
@@ -56,7 +58,7 @@
|
||||
"react-chartjs-2": "^2.7.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-table": "^6.10.0",
|
||||
"reactstrap": "^8.0.0",
|
||||
"sha.js": "^2.4.11",
|
||||
@@ -74,6 +76,7 @@
|
||||
"generate-language-list": "node scripts/generate-language-list.js",
|
||||
"make-fake-data": "node scripts/make-fake-data.js",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"coverage": "react-scripts test --env=jsdom --coverage",
|
||||
"eject": "react-scripts eject",
|
||||
"format": "prettier --write \"src/**/*.tsx\"",
|
||||
"check-format": "prettier --list-different \"src/**/*.tsx\""
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@
|
||||
<!-- End Single Page Apps for GitHub Pages -->
|
||||
</head>
|
||||
|
||||
<body class="app header-fixed sidebar-fixed sidebar-show sidebar-lg-show boxcontainer background-image">
|
||||
<body class="app header-fixed sidebar-fixed sidebar-lg-show boxcontainer background-image">
|
||||
<noscript>
|
||||
<p align="center">Please enable JavaScript! This web interface will not work without it.</p>
|
||||
</noscript>
|
||||
|
||||
@@ -12,19 +12,19 @@ import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const FooterUpdateStatus = (props: WithTranslation) => {
|
||||
const { t } = props;
|
||||
const updateAvailable = false;
|
||||
|
||||
if (updateAvailable) {
|
||||
return (
|
||||
<div className="ml-auto">
|
||||
<Link to="/settings/versions">{t("Update Available")}</Link>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const FooterUpdateStatus = ({
|
||||
updateAvailable = false,
|
||||
t
|
||||
}: { updateAvailable: boolean } & WithTranslation) => {
|
||||
if (!updateAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ml-auto">
|
||||
<Link to="/settings/versions">{t("Update Available")}</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTranslation("footer")(FooterUpdateStatus);
|
||||
|
||||
@@ -15,14 +15,10 @@ import React, { MouseEventHandler } from "react";
|
||||
export interface NavButtonProps {
|
||||
name: string;
|
||||
icon: string;
|
||||
onClick: MouseEventHandler;
|
||||
onClick?: MouseEventHandler;
|
||||
}
|
||||
|
||||
export default ({
|
||||
name,
|
||||
icon,
|
||||
onClick = e => e.preventDefault()
|
||||
}: NavButtonProps) => (
|
||||
export default ({ name, icon, onClick = () => {} }: NavButtonProps) => (
|
||||
<li className="nav-item">
|
||||
<a
|
||||
href="#"
|
||||
|
||||
@@ -84,8 +84,7 @@ describe("EnableDisable", () => {
|
||||
wrapper
|
||||
.find(NavButton)
|
||||
.at(buttonIndex)
|
||||
.props()
|
||||
.onClick({} as MouseEvent);
|
||||
.props().onClick!({} as MouseEvent);
|
||||
|
||||
// Wait for the setStatus promise to resolve
|
||||
await tick();
|
||||
@@ -129,10 +128,7 @@ describe("EnableDisable", () => {
|
||||
|
||||
wrapper.setState({ processing: true });
|
||||
|
||||
wrapper
|
||||
.find(NavButton)
|
||||
.props()
|
||||
.onClick({} as MouseEvent);
|
||||
wrapper.find(NavButton).props().onClick!({} as MouseEvent);
|
||||
|
||||
expect(setStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -151,10 +147,7 @@ describe("EnableDisable", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find(NavButton)
|
||||
.props()
|
||||
.onClick({} as MouseEvent);
|
||||
wrapper.find(NavButton).props().onClick!({} as MouseEvent);
|
||||
|
||||
wrapper.unmount();
|
||||
|
||||
@@ -175,10 +168,7 @@ describe("EnableDisable", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find(NavButton)
|
||||
.props()
|
||||
.onClick({} as MouseEvent);
|
||||
wrapper.find(NavButton).props().onClick!({} as MouseEvent);
|
||||
|
||||
expect(wrapper.state().processing).toBeTruthy();
|
||||
|
||||
@@ -226,8 +216,7 @@ describe("EnableDisable", () => {
|
||||
wrapper
|
||||
.find(NavButton)
|
||||
.last()
|
||||
.props()
|
||||
.onClick({} as MouseEvent);
|
||||
.props().onClick!({} as MouseEvent);
|
||||
|
||||
expect(wrapper.find(Modal).props().isOpen).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* FooterUpdateStatus component test
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import { shallow } from "enzyme";
|
||||
import FooterUpdateStatus from "../FooterUpdateStatus";
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
it("renders as null if no update is available", () => {
|
||||
const wrapper = shallow(<FooterUpdateStatus updateAvailable={false} />);
|
||||
|
||||
expect(wrapper).toBeEmptyRender();
|
||||
});
|
||||
|
||||
it("renders a link to the versions page if there is an update", () => {
|
||||
const wrapper = shallow(<FooterUpdateStatus updateAvailable={true} />);
|
||||
|
||||
expect(wrapper.find(Link).props().to).toEqual("/settings/versions");
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* Header component test
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import React, { MouseEvent } from "react";
|
||||
import api from "../../../util/api";
|
||||
import { shallow } from "enzyme";
|
||||
import Header from "../Header";
|
||||
import { TimeRangeSelectorContainer } from "../../dashboard/TimeRangeSelector";
|
||||
|
||||
it("shows the time range selector on the dashboard", () => {
|
||||
api.loggedIn = true;
|
||||
history.pushState({}, "", "/dashboard");
|
||||
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
expect(wrapper.find(TimeRangeSelectorContainer)).toExist();
|
||||
});
|
||||
|
||||
it("does not show the time range selector when not logged in", () => {
|
||||
api.loggedIn = false;
|
||||
history.pushState({}, "", "/dashboard");
|
||||
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
expect(wrapper.find(TimeRangeSelectorContainer)).not.toExist();
|
||||
});
|
||||
|
||||
it("does not show the time range selector on non-dashboard pages", () => {
|
||||
api.loggedIn = true;
|
||||
history.pushState({}, "", "/whitelist");
|
||||
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
expect(wrapper.find(TimeRangeSelectorContainer)).not.toExist();
|
||||
});
|
||||
|
||||
it("toggle the sidebar when clicked", () => {
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
expect(document.body.classList).not.toContain("sidebar-minimized");
|
||||
expect(document.body.classList).not.toContain("brand-minimized");
|
||||
|
||||
wrapper
|
||||
.find(".sidebar-toggler")
|
||||
.simulate("click", { preventDefault: () => {} });
|
||||
|
||||
expect(document.body.classList).toContain("sidebar-minimized");
|
||||
expect(document.body.classList).toContain("brand-minimized");
|
||||
});
|
||||
|
||||
it("toggle the sidebar on mobile when clicked", () => {
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
expect(document.body.classList).not.toContain("sidebar-show");
|
||||
|
||||
wrapper
|
||||
.find(".navbar-toggler")
|
||||
.first()
|
||||
.simulate("click", { preventDefault: () => {} });
|
||||
|
||||
expect(document.body.classList).toContain("sidebar-show");
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* LanguageApplier component test
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import { mount } from "enzyme";
|
||||
import * as React from "react";
|
||||
import LanguageApplier from "../LanguageApplier";
|
||||
import {
|
||||
PreferencesContext,
|
||||
PreferencesContextType
|
||||
} from "../context/PreferencesContext";
|
||||
import i18n from "i18next";
|
||||
|
||||
it("does not update i18next if the language is already set correctly", () => {
|
||||
const settings: PreferencesContextType = {
|
||||
settings: {
|
||||
language: "en",
|
||||
layout: "boxed"
|
||||
},
|
||||
refresh: () => {}
|
||||
};
|
||||
|
||||
i18n.language = "en";
|
||||
i18n.changeLanguage = jest.fn();
|
||||
|
||||
mount(
|
||||
<PreferencesContext.Provider value={settings}>
|
||||
<LanguageApplier />
|
||||
</PreferencesContext.Provider>
|
||||
);
|
||||
|
||||
expect(i18n.changeLanguage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates i18next with the language if it was using a different one", () => {
|
||||
const settings: PreferencesContextType = {
|
||||
settings: {
|
||||
language: "es",
|
||||
layout: "boxed"
|
||||
},
|
||||
refresh: () => {}
|
||||
};
|
||||
|
||||
i18n.language = "en";
|
||||
i18n.changeLanguage = jest.fn();
|
||||
|
||||
mount(
|
||||
<PreferencesContext.Provider value={settings}>
|
||||
<LanguageApplier />
|
||||
</PreferencesContext.Provider>
|
||||
);
|
||||
|
||||
expect(i18n.changeLanguage).toHaveBeenCalledWith("es");
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* LayoutApplier component test
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import LayoutApplier from "../LayoutApplier";
|
||||
import {
|
||||
PreferencesContext,
|
||||
PreferencesContextType
|
||||
} from "../context/PreferencesContext";
|
||||
|
||||
it("adds box layout CSS when the layout is box", () => {
|
||||
const settings: PreferencesContextType = {
|
||||
settings: {
|
||||
language: "en",
|
||||
layout: "boxed"
|
||||
},
|
||||
refresh: () => {}
|
||||
};
|
||||
|
||||
expect(document.body.classList).not.toContain("boxcontainer");
|
||||
expect(document.body.classList).not.toContain("background-image");
|
||||
|
||||
mount(
|
||||
<PreferencesContext.Provider value={settings}>
|
||||
<LayoutApplier />
|
||||
</PreferencesContext.Provider>
|
||||
);
|
||||
|
||||
expect(document.body.classList).toContain("boxcontainer");
|
||||
expect(document.body.classList).toContain("background-image");
|
||||
});
|
||||
|
||||
it("removes box layout CSS when the layout is traditional", () => {
|
||||
const settings: PreferencesContextType = {
|
||||
settings: {
|
||||
language: "en",
|
||||
layout: "traditional"
|
||||
},
|
||||
refresh: () => {}
|
||||
};
|
||||
|
||||
document.body.classList.add("boxcontainer", "background-image");
|
||||
|
||||
expect(document.body.classList).toContain("boxcontainer");
|
||||
expect(document.body.classList).toContain("background-image");
|
||||
|
||||
mount(
|
||||
<PreferencesContext.Provider value={settings}>
|
||||
<LayoutApplier />
|
||||
</PreferencesContext.Provider>
|
||||
);
|
||||
|
||||
expect(document.body.classList).not.toContain("boxcontainer");
|
||||
expect(document.body.classList).not.toContain("background-image");
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* NavButton component test
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import NavButton from "../NavButton";
|
||||
|
||||
it("renders with the given icon and name", () => {
|
||||
const name = "testName";
|
||||
const icon = "testIcon";
|
||||
const wrapper = shallow(<NavButton name={name} icon={icon} />);
|
||||
|
||||
expect(wrapper).toHaveText(name);
|
||||
expect(wrapper.find("i")).toHaveClassName(icon);
|
||||
});
|
||||
|
||||
it("calls onClick when clicked", () => {
|
||||
const onClick = jest.fn();
|
||||
const event = { preventDefault: jest.fn(), test: true };
|
||||
const wrapper = shallow(
|
||||
<NavButton name="name" icon="icon" onClick={onClick} />
|
||||
);
|
||||
|
||||
wrapper.find("a").simulate("click", event);
|
||||
|
||||
expect(onClick).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it("has a default onClick", () => {
|
||||
const event = { preventDefault: jest.fn() };
|
||||
const wrapper = shallow(<NavButton name="name" icon="icon" />);
|
||||
|
||||
wrapper.find("a").simulate("click", event);
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* Web Interface
|
||||
* NavDropdown component test
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import NavDropdown from "../NavDropdown";
|
||||
|
||||
it("renders as open when isOpen is true", () => {
|
||||
const wrapper = shallow(
|
||||
<NavDropdown name="test" icon="test" isOpen={true}>
|
||||
{}
|
||||
</NavDropdown>
|
||||
);
|
||||
|
||||
expect(wrapper.find("li")).toHaveClassName("open");
|
||||
});
|
||||
|
||||
it("renders as closed when isOpen is false", () => {
|
||||
const wrapper = shallow(
|
||||
<NavDropdown name="test" icon="test" isOpen={false}>
|
||||
{}
|
||||
</NavDropdown>
|
||||
);
|
||||
|
||||
expect(wrapper.find("li")).not.toHaveClassName("open");
|
||||
});
|
||||
|
||||
it("toggles the dropdown when clicked", () => {
|
||||
const toggle = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<NavDropdown name="test" icon="test" isOpen={true}>
|
||||
{}
|
||||
</NavDropdown>
|
||||
);
|
||||
|
||||
expect(wrapper.find("li")).toHaveClassName("open");
|
||||
|
||||
wrapper.find("button").simulate("click", {
|
||||
preventDefault: () => {},
|
||||
target: {
|
||||
parentElement: {
|
||||
classList: {
|
||||
toggle
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(toggle).toHaveBeenCalledWith("open");
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import Sidebar, { NavList, PiholeNavDropdown, PiholeNavItem } from "../Sidebar";
|
||||
import api from "../../../util/api";
|
||||
import { RouteCustomItem, RouteGroup } from "../../../routes";
|
||||
import NavDropdown from "../NavDropdown";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
const t = global.t;
|
||||
|
||||
@@ -192,3 +193,25 @@ it("renders the NavList in the sidebar", () => {
|
||||
expect(props.items).toEqual([item]);
|
||||
expect(props.location).toEqual(location);
|
||||
});
|
||||
|
||||
it("should hide the sidebar on mobile when an item is clicked", () => {
|
||||
const item = {
|
||||
url: "/testUrl",
|
||||
icon: "test-icon",
|
||||
name: "testName",
|
||||
auth: false,
|
||||
component: () => <div />
|
||||
};
|
||||
const wrapper = shallow(<PiholeNavItem item={item} t={global.t} />);
|
||||
|
||||
document.body.classList.add("sidebar-show");
|
||||
expect(document.body.classList).toContain("sidebar-show");
|
||||
|
||||
// @ts-ignore
|
||||
wrapper
|
||||
.find(NavLink)
|
||||
.props()
|
||||
.onClick();
|
||||
|
||||
expect(document.body.classList).not.toContain("sidebar-show");
|
||||
});
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { TranslatedStatusBadge } from "../StatusBadge";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import StatusBadge, { TranslatedStatusBadge } from "../StatusBadge";
|
||||
import { StatusContext, StatusContextType } from "../context/StatusContext";
|
||||
|
||||
it("shows green enabled message if API returns enabled", async () => {
|
||||
const wrapper = shallow(<TranslatedStatusBadge status="enabled" />);
|
||||
@@ -25,3 +26,19 @@ it("shows red disabled message if API doesn't return enabled", async () => {
|
||||
expect(wrapper.childAt(2)).toHaveText("Disabled");
|
||||
expect(wrapper.childAt(0)).toHaveClassName("text-danger");
|
||||
});
|
||||
|
||||
it("uses context to get status", () => {
|
||||
const context: StatusContextType = {
|
||||
status: "enabled",
|
||||
refresh: () => {}
|
||||
};
|
||||
const wrapper = mount(
|
||||
<StatusContext.Provider value={context}>
|
||||
<StatusBadge />
|
||||
</StatusContext.Provider>
|
||||
);
|
||||
|
||||
expect(wrapper.find(TranslatedStatusBadge).props().status).toEqual(
|
||||
context.status
|
||||
);
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import i18n from "i18next";
|
||||
import i18next from "i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import debounce from "lodash.debounce";
|
||||
import isEqual from "lodash.isequal";
|
||||
import moment from "moment";
|
||||
import { padNumber } from "../../util/graphUtils";
|
||||
import api from "../../util/api";
|
||||
@@ -201,17 +202,15 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
|
||||
* @param pageSize The number of queries in the page
|
||||
*/
|
||||
fetchQueries = ({ page, pageSize }: { page: number; pageSize: number }) => {
|
||||
// Check if we've reached the end of the queries, or are still waiting for
|
||||
// the last fetch to finish
|
||||
if (this.state.atEnd || this.state.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the filters are the same and we already have this page and the
|
||||
// next page.
|
||||
// Don't fetch the queries if:
|
||||
// - We've reached the end of the queries
|
||||
// - We are still waiting for the last fetch to finish
|
||||
// - Filters are the same and we already have this page and the next
|
||||
if (
|
||||
!this.state.filtersChanged &&
|
||||
this.state.history.length >= (page + 2) * pageSize
|
||||
this.state.atEnd ||
|
||||
this.state.loading ||
|
||||
(!this.state.filtersChanged &&
|
||||
this.state.history.length >= (page + 2) * pageSize)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -255,7 +254,7 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
|
||||
data={this.state.history}
|
||||
loading={this.state.loading}
|
||||
onFetchData={state => {
|
||||
if (state.filtered === this.state.filters) {
|
||||
if (isEqual(state.filtered, this.state.filters)) {
|
||||
// If the filters have not changed, do not debounce the fetch.
|
||||
// This allows fetching the next page to happen without waiting for
|
||||
// the debounce.
|
||||
@@ -267,6 +266,10 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
|
||||
}
|
||||
}}
|
||||
onFilteredChange={debounce(filters => {
|
||||
if (isEqual(filters, this.state.filters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
filters,
|
||||
filtersChanged: true,
|
||||
@@ -276,6 +279,12 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
|
||||
history: []
|
||||
});
|
||||
}, 300)}
|
||||
defaultFiltered={[
|
||||
{
|
||||
id: "time",
|
||||
value: getDefaultRange(t)
|
||||
}
|
||||
]}
|
||||
getTrProps={this.getRowProps}
|
||||
ofText={this.state.atEnd ? "of" : "of at least"}
|
||||
// Pad empty rows to have the same height as filled rows
|
||||
|
||||
@@ -219,3 +219,9 @@ body {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.modal-dialog {
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import { padNumber, getIntervalForRange } from "../graphUtils";
|
||||
import { TimeRange } from "../../components/common/context/TimeRangeContext";
|
||||
import moment from "moment";
|
||||
|
||||
// A timestamp used for testing
|
||||
const timestamp = 1559614028;
|
||||
|
||||
describe("padNumber", () => {
|
||||
it("pads 0 to 00", () => {
|
||||
expect(padNumber(0)).toEqual("00");
|
||||
@@ -30,8 +33,8 @@ describe("getIntervalForRange", () => {
|
||||
it("returns 10 minutes for 24 hours", () => {
|
||||
const range: TimeRange = {
|
||||
name: "24 Hours",
|
||||
from: moment().subtract(1, "day"),
|
||||
until: moment()
|
||||
from: moment(timestamp).subtract(1, "day"),
|
||||
until: moment(timestamp)
|
||||
};
|
||||
|
||||
expect(getIntervalForRange(range)).toEqual(10 * 60);
|
||||
@@ -42,8 +45,8 @@ describe("getIntervalForRange", () => {
|
||||
name: "144 days",
|
||||
// Use seconds instead of days to ensure the difference in epoch time is
|
||||
// equal to 144 days
|
||||
from: moment().subtract(144 * 24 * 60 * 60, "seconds"),
|
||||
until: moment()
|
||||
from: moment(timestamp).subtract(144 * 24 * 60 * 60, "seconds"),
|
||||
until: moment(timestamp)
|
||||
};
|
||||
|
||||
expect(getIntervalForRange(range)).toEqual(24 * 60 * 60);
|
||||
|
||||
Reference in New Issue
Block a user