Merge branch 'master' into feature/defaultsearchprovider
This commit is contained in:
commit
0ce5d3aea6
53 changed files with 2934 additions and 1856 deletions
58
.circleci/config.yml
Normal file
58
.circleci/config.yml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
version: 2.1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
install_dependencies:
|
||||||
|
description: "Installs dependencies and uses CircleCI's cache"
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- dependencies-{{ checksum "yarn.lock" }}
|
||||||
|
- dependencies-
|
||||||
|
- run:
|
||||||
|
command: |
|
||||||
|
yarn install
|
||||||
|
- save_cache:
|
||||||
|
paths:
|
||||||
|
- node_modules
|
||||||
|
key: dependencies-{{ checksum "yarn.lock" }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
style:
|
||||||
|
docker:
|
||||||
|
- image: node:latest
|
||||||
|
steps:
|
||||||
|
- install_dependencies
|
||||||
|
- run:
|
||||||
|
name: prettier
|
||||||
|
command: |
|
||||||
|
yarn prettier --check
|
||||||
|
- run:
|
||||||
|
name: lint
|
||||||
|
command: |
|
||||||
|
yarn lint
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
docker:
|
||||||
|
- image: node:latest
|
||||||
|
steps:
|
||||||
|
- install_dependencies
|
||||||
|
- run:
|
||||||
|
name: typecheck
|
||||||
|
command: |
|
||||||
|
yarn typecheck
|
||||||
|
- run:
|
||||||
|
name: test
|
||||||
|
command: |
|
||||||
|
yarn test
|
||||||
|
- run:
|
||||||
|
name: coverage
|
||||||
|
command: |
|
||||||
|
yarn coverage
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
dashboard:
|
||||||
|
jobs:
|
||||||
|
- style
|
||||||
|
- dashboard
|
9
.eslintrc.js
Normal file
9
.eslintrc.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ["eslint:recommended", "plugin:react-hooks/recommended"],
|
||||||
|
rules: {
|
||||||
|
maxClassesPerFile: 0,
|
||||||
|
maxLineLength: 0,
|
||||||
|
memberOrdering: 0,
|
||||||
|
variableName: 0,
|
||||||
|
},
|
||||||
|
};
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -21,3 +21,5 @@
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
|
||||||
|
|
21
.prettierrc.js
Normal file
21
.prettierrc.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
module.exports = {
|
||||||
|
bracketSpacing: true,
|
||||||
|
printWidth: 80,
|
||||||
|
parser: "typescript",
|
||||||
|
trailingComma: "all",
|
||||||
|
arrowParens: "always",
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: "README.md",
|
||||||
|
options: {
|
||||||
|
parser: "markdown",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: "*.json",
|
||||||
|
options: {
|
||||||
|
parser: "json",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
19
README.md
19
README.md
|
@ -1,5 +1,12 @@
|
||||||
# Dashboard
|
# Dashboard
|
||||||
|
|
||||||
|
[![CircleCI][shield-circleci]][circleci]
|
||||||
|
[![Docker Cloud Build Status][shield-docker]][docker]
|
||||||
|
[![Docker Image Size (latest)][shield-docker-image]][docker]
|
||||||
|
[![codecov][shield-codecov]][codecov]
|
||||||
|
[![Dependencies][shield-deps]][repo]
|
||||||
|
[![GitHub license][shield-license]][license]
|
||||||
|
|
||||||
![Alt text](/screenshot.png?raw=true "screenshot")
|
![Alt text](/screenshot.png?raw=true "screenshot")
|
||||||
|
|
||||||
Dashboard is just that - a dashboard. It's inspired by [SUI](https://github.com/jeroenpardon/sui) and has all the same features as SUI, such as simple customization through JSON-files and a handy search bar to search the internet more efficiently.
|
Dashboard is just that - a dashboard. It's inspired by [SUI](https://github.com/jeroenpardon/sui) and has all the same features as SUI, such as simple customization through JSON-files and a handy search bar to search the internet more efficiently.
|
||||||
|
@ -199,3 +206,15 @@ In order for the imprint-modal to show up, make sure your `imprint.json` resembl
|
||||||
```
|
```
|
||||||
|
|
||||||
> :exclamation: I haven't quite tested this. I'm not a lawyer and I'm not responsible if you're sued for using this incorrectly.
|
> :exclamation: I haven't quite tested this. I'm not a lawyer and I'm not responsible if you're sued for using this incorrectly.
|
||||||
|
|
||||||
|
[docker]: https://hub.docker.com/r/phntxx/dashboard
|
||||||
|
[codecov]: https://codecov.io/gh/phntxx/dashboard
|
||||||
|
[repo]: https://github.com/phntxx/dashboard
|
||||||
|
[license]: https://github.com/phntxx/dashboard/LICENSE
|
||||||
|
[circleci]: https://circleci.com/gh/phntxx/dashboard
|
||||||
|
[shield-docker]: https://img.shields.io/docker/cloud/build/phntxx/dashboard
|
||||||
|
[shield-docker-image]: https://img.shields.io/docker/image-size/phntxx/dashboard/latest
|
||||||
|
[shield-circleci]: https://circleci.com/gh/phntxx/dashboard.svg?style=shield
|
||||||
|
[shield-codecov]: https://codecov.io/gh/phntxx/dashboard/branch/master/graph/badge.svg
|
||||||
|
[shield-license]: https://img.shields.io/github/license/phntxx/dashboard.svg
|
||||||
|
[shield-deps]: https://img.shields.io/david/phntxx/dashboard
|
||||||
|
|
13
codecov.yml
Normal file
13
codecov.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default: off
|
||||||
|
dashboard:
|
||||||
|
target: 80%
|
||||||
|
flags: dashboard
|
||||||
|
|
||||||
|
flags:
|
||||||
|
dashboard:
|
||||||
|
paths:
|
||||||
|
- src/components/
|
||||||
|
- src/lib/
|
|
@ -42,7 +42,7 @@
|
||||||
{
|
{
|
||||||
"greeting": "Good evening!",
|
"greeting": "Good evening!",
|
||||||
"start": 18,
|
"start": 18,
|
||||||
"end": 0
|
"end": 24
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dateformat": "%wd, %m %d%e %y"
|
"dateformat": "%wd, %m %d%e %y"
|
||||||
|
|
26
package.json
26
package.json
|
@ -8,14 +8,13 @@
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@testing-library/jest-dom": "^5.11.10",
|
|
||||||
"@testing-library/react": "^11.2.5",
|
|
||||||
"@testing-library/user-event": "^13.0.6",
|
|
||||||
"@types/jest": "^26.0.22",
|
|
||||||
"@types/node": "^14.14.37",
|
"@types/node": "^14.14.37",
|
||||||
"@types/react-dom": "^17.0.3",
|
"@types/react-dom": "^17.0.3",
|
||||||
"@types/react-select": "^4.0.13",
|
"@types/react-select": "^4.0.13",
|
||||||
"@types/styled-components": "^5.1.9",
|
"@types/styled-components": "^5.1.9",
|
||||||
|
"browserslist": "^4.16.6",
|
||||||
|
"http-server": "^0.12.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "^4.0.3",
|
"react-scripts": "^4.0.3",
|
||||||
|
@ -23,11 +22,28 @@
|
||||||
"styled-components": "^5.2.1",
|
"styled-components": "^5.2.1",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.11.10",
|
||||||
|
"@testing-library/react": "^11.2.7",
|
||||||
|
"@testing-library/user-event": "^13.0.6",
|
||||||
|
"@types/jest": "^26.0.22",
|
||||||
|
"codecov": "^3.8.2",
|
||||||
|
"eslint": "^7.28.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
|
"prettier": "^2.3.1"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
|
"coverage": "codecov -f coverage/*.json -F dashboard",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"lint": "eslint --config .eslintrc.js",
|
||||||
|
"prettier": "prettier --config .prettierrc.js '{data,src}/**/*.{json,ts,tsx}'",
|
||||||
|
"http-server:data": "http-server ./ -c-1",
|
||||||
|
"http-server:app": "http-server ./build --proxy http://localhost:8080 --port 3000",
|
||||||
|
"serve:production": "npm-run-all --parallel http-server:data http-server:app"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
|
|
25
src/app.tsx
25
src/app.tsx
|
@ -3,7 +3,7 @@ import { createGlobalStyle } from "styled-components";
|
||||||
import SearchBar from "./components/searchBar";
|
import SearchBar from "./components/searchBar";
|
||||||
import Greeter from "./components/greeter";
|
import Greeter from "./components/greeter";
|
||||||
import AppList from "./components/appList";
|
import AppList from "./components/appList";
|
||||||
import BookmarkList from "./components/bookmarkList";
|
import BookmarkList from "./components/bookmarks";
|
||||||
import Settings from "./components/settings";
|
import Settings from "./components/settings";
|
||||||
import Imprint from "./components/imprint";
|
import Imprint from "./components/imprint";
|
||||||
|
|
||||||
|
@ -29,20 +29,27 @@ const GlobalStyle = createGlobalStyle`
|
||||||
* Renders the entire app by calling individual components
|
* Renders the entire app by calling individual components
|
||||||
*/
|
*/
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const {
|
||||||
const { appData, bookmarkData, searchProviderData, themeData, imprintData, greeterData } = useFetcher();
|
appData,
|
||||||
|
bookmarkData,
|
||||||
|
searchProviderData,
|
||||||
|
themeData,
|
||||||
|
imprintData,
|
||||||
|
greeterData,
|
||||||
|
} = useFetcher();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<div>
|
<div>
|
||||||
<SearchBar search={searchProviderData?.search} />
|
<SearchBar search={searchProviderData?.search} />
|
||||||
{!themeData.error && !searchProviderData.error && (
|
{!themeData.error ||
|
||||||
<Settings
|
(!searchProviderData.error && (
|
||||||
themes={themeData?.themes}
|
<Settings
|
||||||
search={searchProviderData?.search}
|
themes={themeData?.themes}
|
||||||
/>
|
search={searchProviderData?.search}
|
||||||
)}
|
/>
|
||||||
|
))}
|
||||||
<Greeter data={greeterData.greeter} />
|
<Greeter data={greeterData.greeter} />
|
||||||
{!appData.error && (
|
{!appData.error && (
|
||||||
<AppList apps={appData.apps} categories={appData.categories} />
|
<AppList apps={appData.apps} categories={appData.categories} />
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import Icon from "./icon";
|
import Icon from "./icon";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
import selectedTheme from "../lib/theme";
|
||||||
|
|
||||||
const AppContainer = styled.div`
|
const AppContainer = styled.a`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: auto 25%;
|
flex: 1 0 auto;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
color: ${selectedTheme.mainColor};
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const IconContainer = styled.div`
|
const IconContainer = styled.div`
|
||||||
|
@ -21,16 +26,8 @@ const DetailsContainer = styled.div`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AppLink = styled.a`
|
const AppName = styled.div`
|
||||||
flex: 1 0 auto;
|
a:hover & {
|
||||||
color: ${selectedTheme.mainColor};
|
|
||||||
font-weight: 500;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin: 0;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -53,26 +50,29 @@ export interface IAppProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a single app shortcut
|
* Renders a single app shortcut
|
||||||
* @param {IAppProps} props - The props of the given app
|
* @param {IAppProps} props the props of the given app
|
||||||
|
* @returns {React.ReactNode} the child node for the given app
|
||||||
*/
|
*/
|
||||||
export const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
|
const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
|
||||||
|
const linkAttrs =
|
||||||
useEffect(() => { console.log(newTab) }, [newTab])
|
newTab !== undefined && newTab
|
||||||
|
? {
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noopener noreferrer",
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContainer>
|
<AppContainer href={url} {...linkAttrs}>
|
||||||
<IconContainer>
|
<IconContainer>
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
<DetailsContainer>
|
<DetailsContainer>
|
||||||
|
<AppName>{name}</AppName>
|
||||||
{
|
|
||||||
(newTab !== undefined && newTab) ?
|
|
||||||
<AppLink href={url} target="_blank" rel="noopener noreferrer">{name}</AppLink> : <AppLink href={url}>{name}</AppLink>
|
|
||||||
}
|
|
||||||
|
|
||||||
<AppDescription>{displayURL}</AppDescription>
|
<AppDescription>{displayURL}</AppDescription>
|
||||||
</DetailsContainer>
|
</DetailsContainer>
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { App, IAppProps } from "./app";
|
import App, { IAppProps } from "./app";
|
||||||
import { ItemList, Item, SubHeadline } from "./elements";
|
import { ItemList, Item, SubHeadline } from "./elements";
|
||||||
|
|
||||||
const CategoryHeadline = styled(SubHeadline)`
|
const CategoryHeadline = styled(SubHeadline)`
|
||||||
|
@ -18,9 +17,10 @@ export interface IAppCategoryProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders one app category
|
* Renders one app category
|
||||||
* @param {IAppCategoryProps} props - The props of the given category
|
* @param {IAppCategoryProps} props props of the given category
|
||||||
|
* @returns {React.ReactNode} the app category node
|
||||||
*/
|
*/
|
||||||
export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
||||||
<CategoryContainer>
|
<CategoryContainer>
|
||||||
{name && <CategoryHeadline>{name}</CategoryHeadline>}
|
{name && <CategoryHeadline>{name}</CategoryHeadline>}
|
||||||
<ItemList>
|
<ItemList>
|
||||||
|
@ -38,3 +38,5 @@ export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
||||||
</ItemList>
|
</ItemList>
|
||||||
</CategoryContainer>
|
</CategoryContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export default AppCategory;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppCategory, IAppCategoryProps } from "./appCategory";
|
import AppCategory, { IAppCategoryProps } from "./appCategory";
|
||||||
import { IAppProps } from "./app";
|
import { IAppProps } from "./app";
|
||||||
|
|
||||||
import { Headline, ListContainer } from "./elements";
|
import { Headline, ListContainer } from "./elements";
|
||||||
|
@ -10,7 +10,8 @@ export interface IAppListProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders one list containing all app categories and uncategorized apps
|
* Renders one list containing all app categories and uncategorized apps
|
||||||
* @param {IAppListProps} props - The props of the given list of apps
|
* @param {IAppListProps} props props of the given list of apps
|
||||||
|
* @returns {React.ReactNode} the app list component
|
||||||
*/
|
*/
|
||||||
const AppList = ({ categories, apps }: IAppListProps) => (
|
const AppList = ({ categories, apps }: IAppListProps) => (
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
|
@ -20,10 +21,7 @@ const AppList = ({ categories, apps }: IAppListProps) => (
|
||||||
<AppCategory key={[name, idx].join("")} name={name} items={items} />
|
<AppCategory key={[name, idx].join("")} name={name} items={items} />
|
||||||
))}
|
))}
|
||||||
{apps && (
|
{apps && (
|
||||||
<AppCategory
|
<AppCategory name={categories ? "Uncategorized apps" : ""} items={apps} />
|
||||||
name={categories ? "Uncategorized apps" : ""}
|
|
||||||
items={apps}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Headline, ListContainer, ItemList } from "./elements";
|
|
||||||
import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup";
|
|
||||||
|
|
||||||
interface IBookmarkListProps {
|
|
||||||
groups: Array<IBookmarkGroupProps>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a given list of categorized bookmarks
|
|
||||||
* @param {IBookmarkListProps} props - The props of the given bookmark list
|
|
||||||
*/
|
|
||||||
const BookmarkList = ({ groups }: IBookmarkListProps) => (
|
|
||||||
<ListContainer>
|
|
||||||
<Headline>Bookmarks</Headline>
|
|
||||||
<ItemList>
|
|
||||||
{groups.map(({ name, items }, idx) => (
|
|
||||||
<BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
|
|
||||||
))}
|
|
||||||
</ItemList>
|
|
||||||
</ListContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default BookmarkList;
|
|
|
@ -1,6 +1,11 @@
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Item, SubHeadline } from "./elements";
|
import {
|
||||||
|
Headline,
|
||||||
|
Item,
|
||||||
|
ItemList,
|
||||||
|
ListContainer,
|
||||||
|
SubHeadline,
|
||||||
|
} from "./elements";
|
||||||
import selectedTheme from "../lib/theme";
|
import selectedTheme from "../lib/theme";
|
||||||
|
|
||||||
const GroupContainer = styled.div`
|
const GroupContainer = styled.div`
|
||||||
|
@ -32,9 +37,14 @@ export interface IBookmarkGroupProps {
|
||||||
items: Array<IBookmarkProps>;
|
items: Array<IBookmarkProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IBookmarkListProps {
|
||||||
|
groups: Array<IBookmarkGroupProps>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a given bookmark group
|
* Renders a given bookmark group
|
||||||
* @param {IBookmarkGroupProps} props - The given props of the bookmark group
|
* @param {IBookmarkGroupProps} props given props of the bookmark group
|
||||||
|
* @returns {React.ReactNode} the bookmark group component
|
||||||
*/
|
*/
|
||||||
export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
||||||
<Item>
|
<Item>
|
||||||
|
@ -48,3 +58,21 @@ export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
||||||
</GroupContainer>
|
</GroupContainer>
|
||||||
</Item>
|
</Item>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a given list of categorized bookmarks
|
||||||
|
* @param {IBookmarkListProps} props props of the given bookmark list
|
||||||
|
* @returns {React.ReactNode} the bookmark list component
|
||||||
|
*/
|
||||||
|
const BookmarkList = ({ groups }: IBookmarkListProps) => (
|
||||||
|
<ListContainer>
|
||||||
|
<Headline>Bookmarks</Headline>
|
||||||
|
<ItemList>
|
||||||
|
{groups.map(({ name, items }, idx) => (
|
||||||
|
<BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
|
||||||
|
))}
|
||||||
|
</ItemList>
|
||||||
|
</ListContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BookmarkList;
|
|
@ -1,7 +1,5 @@
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
import selectedTheme from "../lib/theme";
|
||||||
import Icon from "./icon";
|
|
||||||
|
|
||||||
export const ListContainer = styled.div`
|
export const ListContainer = styled.div`
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
|
@ -56,29 +54,3 @@ export const Button = styled.button`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
|
||||||
float: right;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface IIconButtonProps {
|
|
||||||
icon: string;
|
|
||||||
onClick: (e: React.FormEvent) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a button with an icon
|
|
||||||
* @param {IIconProps} props - The props of the given IconButton
|
|
||||||
*/
|
|
||||||
export const IconButton = ({ icon, onClick }: IIconButtonProps) => (
|
|
||||||
<StyledButton onClick={onClick}>
|
|
||||||
<Icon name={icon} />
|
|
||||||
</StyledButton>
|
|
||||||
);
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ const DateText = styled.h3`
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${selectedTheme.accentColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export interface IGreeterComponentProps {
|
||||||
|
data: IGreeterProps;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IGreeterProps {
|
export interface IGreeterProps {
|
||||||
months: Array<string>;
|
months: Array<string>;
|
||||||
days: Array<string>;
|
days: Array<string>;
|
||||||
|
@ -33,50 +37,48 @@ interface IGreetingProps {
|
||||||
end: number;
|
end: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGreeterComponentProps {
|
|
||||||
data: IGreeterProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Checks if a number is between two numbers
|
||||||
* @param a the number that's supposed to be checked
|
* @param {number} a number that's supposed to be checked
|
||||||
* @param b the minimum
|
* @param {number} b minimum
|
||||||
* @param c the maximum
|
* @param {number} c maximum
|
||||||
*/
|
*/
|
||||||
const isBetween = (a: number, b: number, c: number): boolean => (a > b && a < c)
|
export const isBetween = (a: number, b: number, c: number): boolean =>
|
||||||
|
a >= b && a <= c;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a greeting based on the current time
|
* Returns a greeting based on the current time
|
||||||
* @returns {string} - A greeting
|
* @param {Array<IGreetingProps>} greetings a list of greetings with start and end date
|
||||||
|
* @returns {string} a greeting
|
||||||
*/
|
*/
|
||||||
const getGreeting = (greetings: Array<IGreetingProps>): string => {
|
export const getGreeting = (greetings: Array<IGreetingProps>): string => {
|
||||||
|
let hours = Math.floor(new Date().getHours());
|
||||||
let hours = Math.floor(new Date().getHours())
|
|
||||||
let result = "";
|
let result = "";
|
||||||
|
|
||||||
greetings.forEach(greeting => {
|
greetings.forEach((greeting) => {
|
||||||
if (isBetween(hours, greeting.start, greeting.end)) result = greeting.greeting;
|
if (isBetween(hours, greeting.start, greeting.end))
|
||||||
})
|
result = greeting.greeting;
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the appropriate extension for a number (eg. 'rd' for '3' to make '3rd')
|
* Returns the appropriate extension for a number (eg. 'rd' for '3' to make '3rd')
|
||||||
* @param {number} day - The number of a day within a month
|
* @param {number} day number of a day within a month
|
||||||
* @returns {string} - The extension for that number
|
* @returns {string} extension for that number
|
||||||
*/
|
*/
|
||||||
const getExtension = (day: number) => {
|
export const getExtension = (day: number) => {
|
||||||
let extension = "";
|
let extension = "";
|
||||||
|
|
||||||
if ((day > 4 && day <= 20) || (day > 20 && day % 10 >= 4)) {
|
if (day % 10 === 1) {
|
||||||
extension = "th";
|
|
||||||
} else if (day % 10 === 1) {
|
|
||||||
extension = "st";
|
extension = "st";
|
||||||
} else if (day % 10 === 2) {
|
} else if (day % 10 === 2) {
|
||||||
extension = "nd";
|
extension = "nd";
|
||||||
} else if (day % 10 === 3) {
|
} else if (day % 10 === 3) {
|
||||||
extension = "rd";
|
extension = "rd";
|
||||||
|
} else if (isBetween(day, 4, 20) || (day > 20 && day % 10 >= 4)) {
|
||||||
|
extension = "th";
|
||||||
}
|
}
|
||||||
|
|
||||||
return extension;
|
return extension;
|
||||||
|
@ -84,10 +86,14 @@ const getExtension = (day: number) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the current date
|
* Generates the current date
|
||||||
* @param {string} format - The format of the date string
|
* @param {string} format format of the date string
|
||||||
* @returns {string} - The current date as a string
|
* @returns {string} current date as a string
|
||||||
*/
|
*/
|
||||||
const getDateString = (weekdays: Array<string>, months: Array<string>, format: string) => {
|
export const getDateString = (
|
||||||
|
weekdays: Array<string>,
|
||||||
|
months: Array<string>,
|
||||||
|
format: string,
|
||||||
|
) => {
|
||||||
let currentDate = new Date();
|
let currentDate = new Date();
|
||||||
|
|
||||||
let weekday = weekdays[currentDate.getUTCDay()];
|
let weekday = weekdays[currentDate.getUTCDay()];
|
||||||
|
@ -96,15 +102,24 @@ const getDateString = (weekdays: Array<string>, months: Array<string>, format: s
|
||||||
let extension = getExtension(day);
|
let extension = getExtension(day);
|
||||||
let year = currentDate.getFullYear();
|
let year = currentDate.getFullYear();
|
||||||
|
|
||||||
return format.replace("%wd", weekday).replace("%d", day.toString()).replace("%e", extension).replace("%m", month).replace("%y", year.toString());
|
return format
|
||||||
|
.replace("%wd", weekday)
|
||||||
|
.replace("%d", day.toString())
|
||||||
|
.replace("%e", extension)
|
||||||
|
.replace("%m", month)
|
||||||
|
.replace("%y", year.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the Greeter
|
* Renders the Greeter
|
||||||
|
* @param {IGreeterComponentProps} data required greeter data
|
||||||
|
* @returns {React.ReactNode} the greeter
|
||||||
*/
|
*/
|
||||||
const Greeter = ({ data }: IGreeterComponentProps) => (
|
const Greeter = ({ data }: IGreeterComponentProps) => (
|
||||||
<GreeterContainer>
|
<GreeterContainer>
|
||||||
<DateText>{getDateString(data.days, data.months, data.dateformat)}</DateText>
|
<DateText>
|
||||||
|
{getDateString(data.days, data.months, data.dateformat)}
|
||||||
|
</DateText>
|
||||||
<GreetText>{getGreeting(data.greetings)}</GreetText>
|
<GreetText>{getGreeting(data.greetings)}</GreetText>
|
||||||
</GreeterContainer>
|
</GreeterContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,32 +7,62 @@ interface IIconProps {
|
||||||
size?: string;
|
size?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IIconButtonProps {
|
||||||
|
testid?: string;
|
||||||
|
icon: string;
|
||||||
|
onClick: (e: React.FormEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledButton = styled.button`
|
||||||
|
float: right;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const IconContainer = styled.i`
|
||||||
|
font-family: "Material Icons";
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-feature-settings: "liga";
|
||||||
|
font-size: ${(props) => props.about};
|
||||||
|
color: ${(props) => props.color};
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders an Icon
|
* Renders an Icon
|
||||||
* @param {IIconProps} props - The props needed for the given icon
|
* @param {IIconProps} props props needed for the given icon
|
||||||
|
* @returns {React.ReactNode} the icon node
|
||||||
*/
|
*/
|
||||||
export const Icon = ({ name, size }: IIconProps) => {
|
export const Icon = ({ name, size }: IIconProps) => (
|
||||||
|
<IconContainer color={selectedTheme.mainColor} about={size}>
|
||||||
|
{name}
|
||||||
|
</IconContainer>
|
||||||
|
);
|
||||||
|
|
||||||
let IconContainer = styled.i`
|
/**
|
||||||
font-family: "Material Icons";
|
* Renders a button with an icon
|
||||||
font-weight: normal;
|
* @param {IIconProps} props - The props of the given IconButton
|
||||||
font-style: normal;
|
* @returns {React.ReactNode} the icon button node
|
||||||
font-size: ${size ? size : "24px"};
|
*/
|
||||||
color: ${selectedTheme.mainColor};
|
export const IconButton = ({ testid, icon, onClick }: IIconButtonProps) => (
|
||||||
display: inline-block;
|
<StyledButton data-testid={testid} onClick={onClick}>
|
||||||
line-height: 1;
|
<Icon name={icon} />
|
||||||
text-transform: none;
|
</StyledButton>
|
||||||
letter-spacing: normal;
|
);
|
||||||
word-wrap: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
direction: ltr;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
font-feature-settings: "liga";
|
|
||||||
`;
|
|
||||||
|
|
||||||
return <IconContainer>{name}</IconContainer>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Icon;
|
export default Icon;
|
||||||
|
|
|
@ -2,12 +2,7 @@ import React from "react";
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
import selectedTheme from "../lib/theme";
|
||||||
import {
|
import { ListContainer, ItemList, Headline, SubHeadline } from "./elements";
|
||||||
ListContainer,
|
|
||||||
ItemList,
|
|
||||||
Headline,
|
|
||||||
SubHeadline,
|
|
||||||
} from "./elements";
|
|
||||||
|
|
||||||
const ModalSubHeadline = styled(SubHeadline)`
|
const ModalSubHeadline = styled(SubHeadline)`
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -37,11 +32,6 @@ const ItemContainer = styled.div`
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface IImprintFieldProps {
|
|
||||||
text: string;
|
|
||||||
link: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IImprintProps {
|
export interface IImprintProps {
|
||||||
name: IImprintFieldProps;
|
name: IImprintFieldProps;
|
||||||
address: IImprintFieldProps;
|
address: IImprintFieldProps;
|
||||||
|
@ -51,25 +41,32 @@ export interface IImprintProps {
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IImprintComponentProps {
|
||||||
|
imprint: IImprintProps;
|
||||||
|
}
|
||||||
|
|
||||||
interface IImprintFieldComponentProps {
|
interface IImprintFieldComponentProps {
|
||||||
field: IImprintFieldProps;
|
field: IImprintFieldProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IImprintComponentProps {
|
interface IImprintFieldProps {
|
||||||
imprint: IImprintProps;
|
text: string;
|
||||||
|
link: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders an imprint field
|
* Renders an imprint field
|
||||||
* @param {IImprintFieldComponentProps} props - The data for the field
|
* @param {IImprintFieldComponentProps} props data for the field
|
||||||
|
* @returns {React.ReactNode} the imprint field component
|
||||||
*/
|
*/
|
||||||
const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
export const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
||||||
<Link href={field.link}>{field.text}</Link>
|
<Link href={field.link}>{field.text}</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the imprint component
|
* Renders the imprint component
|
||||||
* @param {IImprintProps} props - The contents of the imprint
|
* @param {IImprintProps} props contents of the imprint
|
||||||
|
* @returns {React.ReactNode} the imprint node
|
||||||
*/
|
*/
|
||||||
const Imprint = ({ imprint }: IImprintComponentProps) => (
|
const Imprint = ({ imprint }: IImprintComponentProps) => (
|
||||||
<>
|
<>
|
||||||
|
@ -85,15 +82,17 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
||||||
condition={!window.location.href.endsWith("#imprint")}
|
condition={!window.location.href.endsWith("#imprint")}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
if (window.location.href.endsWith("#imprint")) {
|
if (window.location.href.endsWith("#imprint")) {
|
||||||
let location = window.location.href.replace("#imprint", "");
|
window.location.href = window.location.href.replace(
|
||||||
window.location.href = location;
|
"#imprint",
|
||||||
|
"",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<ModalSubHeadline>
|
<ModalSubHeadline>
|
||||||
Information in accordance with section 5 TMG
|
Information in accordance with section 5 TMG
|
||||||
</ModalSubHeadline>
|
</ModalSubHeadline>
|
||||||
<>
|
<>
|
||||||
{imprint.name && <ImprintField field={imprint.name} />}
|
{imprint.name && <ImprintField field={imprint.name} />}
|
||||||
{imprint.address && <ImprintField field={imprint.address} />}
|
{imprint.address && <ImprintField field={imprint.address} />}
|
||||||
|
@ -103,9 +102,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ModalSubHeadline>
|
<ModalSubHeadline>Imprint</ModalSubHeadline>
|
||||||
Imprint
|
|
||||||
</ModalSubHeadline>
|
|
||||||
{imprint.text && <Text>{imprint.text}</Text>}
|
{imprint.text && <Text>{imprint.text}</Text>}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -2,7 +2,8 @@ import React, { useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import selectedTheme from "../lib/theme";
|
import selectedTheme from "../lib/theme";
|
||||||
|
|
||||||
import { Headline, IconButton } from "./elements";
|
import { Headline } from "./elements";
|
||||||
|
import { IconButton } from "./icon";
|
||||||
|
|
||||||
const ModalContainer = styled.div`
|
const ModalContainer = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -37,7 +38,7 @@ const TitleContainer = styled.div`
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface IModalProps {
|
export interface IModalProps {
|
||||||
element: string;
|
element: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
|
@ -49,9 +50,18 @@ interface IModalProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a modal with button to hide and un-hide
|
* Renders a modal with button to hide and un-hide
|
||||||
* @param {IModalProps} props - The needed props for the modal
|
* @param {IModalProps} props needed props for the modal
|
||||||
|
* @returns {React.ReactNode} the modal component
|
||||||
*/
|
*/
|
||||||
const Modal = ({ element, icon, text, condition, title, onClose, children }: IModalProps) => {
|
const Modal = ({
|
||||||
|
element,
|
||||||
|
icon,
|
||||||
|
text,
|
||||||
|
condition,
|
||||||
|
title,
|
||||||
|
onClose,
|
||||||
|
children,
|
||||||
|
}: IModalProps) => {
|
||||||
const [modalHidden, setModalHidden] = useState<boolean>(condition ?? true);
|
const [modalHidden, setModalHidden] = useState<boolean>(condition ?? true);
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
|
@ -62,17 +72,27 @@ const Modal = ({ element, icon, text, condition, title, onClose, children }: IMo
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{element === "icon" && (
|
{element === "icon" && (
|
||||||
<IconButton icon={icon ?? ""} onClick={() => closeModal()} />
|
<IconButton
|
||||||
|
icon={icon ?? ""}
|
||||||
|
testid="toggle-button"
|
||||||
|
onClick={() => closeModal()}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{element === "text" && (
|
{element === "text" && (
|
||||||
<Text onClick={() => closeModal()}>{text}</Text>
|
<Text data-testid="toggle-button" onClick={() => closeModal()}>
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ModalContainer hidden={modalHidden}>
|
<ModalContainer hidden={modalHidden}>
|
||||||
<TitleContainer>
|
<TitleContainer>
|
||||||
<Headline>{title}</Headline>
|
<Headline>{title}</Headline>
|
||||||
<IconButton icon="close" onClick={() => closeModal()} />
|
<IconButton
|
||||||
|
icon="close"
|
||||||
|
testid="close-button"
|
||||||
|
onClick={() => closeModal()}
|
||||||
|
/>
|
||||||
</TitleContainer>
|
</TitleContainer>
|
||||||
{children}
|
{children}
|
||||||
</ModalContainer>
|
</ModalContainer>
|
||||||
|
|
|
@ -16,18 +16,17 @@ const Search = styled.form`
|
||||||
|
|
||||||
const SearchInput = styled.input`
|
const SearchInput = styled.input`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
border-bottom: 1px solid ${selectedTheme.accentColor};
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
background: none;
|
background: none;
|
||||||
border-radius: 0;
|
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${selectedTheme.mainColor};
|
||||||
|
|
||||||
margin: 0px;
|
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +52,30 @@ interface ISearchBarProps {
|
||||||
search: ISearchProps;
|
search: ISearchProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleQueryWithProvider = (
|
||||||
|
search: ISearchProps,
|
||||||
|
query: string,
|
||||||
|
) => {
|
||||||
|
let queryArray: Array<string> = query.split(" ");
|
||||||
|
let prefix: string = queryArray[0];
|
||||||
|
|
||||||
|
queryArray.shift();
|
||||||
|
|
||||||
|
let searchQuery: string = queryArray.join(" ");
|
||||||
|
|
||||||
|
let providerFound: boolean = false;
|
||||||
|
if (search.providers) {
|
||||||
|
search.providers.forEach((provider: ISearchProviderProps) => {
|
||||||
|
if (provider.prefix === prefix) {
|
||||||
|
providerFound = true;
|
||||||
|
window.location.href = provider.url + searchQuery;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!providerFound) window.location.href = search.defaultProvider + query;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a search bar
|
* Renders a search bar
|
||||||
* @param {ISearchBarProps} search - The search providers for the search bar to use
|
* @param {ISearchBarProps} search - The search providers for the search bar to use
|
||||||
|
@ -67,7 +90,7 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
||||||
var query: string = input || "";
|
var query: string = input || "";
|
||||||
|
|
||||||
if (query.split(" ")[0].includes("/")) {
|
if (query.split(" ")[0].includes("/")) {
|
||||||
handleQueryWithProvider(query);
|
handleQueryWithProvider(search, query);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = search.defaultProvider + query;
|
window.location.href = search.defaultProvider + query;
|
||||||
}
|
}
|
||||||
|
@ -75,32 +98,11 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQueryWithProvider = (query: string) => {
|
|
||||||
let queryArray: Array<string> = query.split(" ");
|
|
||||||
let prefix: string = queryArray[0];
|
|
||||||
|
|
||||||
queryArray.shift();
|
|
||||||
|
|
||||||
let searchQuery: string = queryArray.join(" ");
|
|
||||||
|
|
||||||
let providerFound: boolean = false;
|
|
||||||
if (search.providers) {
|
|
||||||
search.providers.forEach((provider: ISearchProviderProps) => {
|
|
||||||
if (provider.prefix === prefix) {
|
|
||||||
providerFound = true;
|
|
||||||
window.location.href = provider.url + searchQuery;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!providerFound)
|
|
||||||
window.location.href = search.defaultProvider + query;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Search onSubmit={(e) => handleSearchQuery(e)}>
|
<Search onSubmit={(e) => handleSearchQuery(e)}>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="search-input"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setInput(e.target.value)
|
setInput(e.target.value)
|
||||||
|
@ -108,12 +110,17 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
||||||
></SearchInput>
|
></SearchInput>
|
||||||
<SearchButton
|
<SearchButton
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="search-clear"
|
||||||
onClick={() => setInput("")}
|
onClick={() => setInput("")}
|
||||||
hidden={buttonsHidden}
|
hidden={buttonsHidden}
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</SearchButton>
|
</SearchButton>
|
||||||
<SearchButton type="submit" hidden={buttonsHidden}>
|
<SearchButton
|
||||||
|
type="submit"
|
||||||
|
data-testid="search-submit"
|
||||||
|
hidden={buttonsHidden}
|
||||||
|
>
|
||||||
Search
|
Search
|
||||||
</SearchButton>
|
</SearchButton>
|
||||||
</Search>
|
</Search>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import Select, { ValueType } from "react-select";
|
import Select, { ValueType } from "react-select";
|
||||||
|
@ -9,36 +9,37 @@ import { Button, SubHeadline } from "./elements";
|
||||||
|
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
|
|
||||||
const FormContainer = styled.div`
|
export const FormContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto auto;
|
grid-template-columns: auto auto auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Table = styled.table`
|
export const Table = styled.table`
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
background: none;
|
background: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${selectedTheme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TableRow = styled.tr`
|
export const TableRow = styled.tr`
|
||||||
border-bottom: 1px solid ${selectedTheme.mainColor};
|
border-bottom: 1px solid ${selectedTheme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TableCell = styled.td`
|
export const TableCell = styled.td`
|
||||||
|
background: none;
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HeadCell = styled.th`
|
export const HeadCell = styled.th`
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Section = styled.div`
|
export const Section = styled.div`
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SectionHeadline = styled(SubHeadline)`
|
export const SectionHeadline = styled(SubHeadline)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
border-bottom: 1px solid ${selectedTheme.accentColor};
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
@ -54,7 +55,7 @@ const Code = styled.p`
|
||||||
color: ${selectedTheme.accentColor};
|
color: ${selectedTheme.accentColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SelectorStyle: any = {
|
export const SelectorStyle: any = {
|
||||||
container: (base: any): any => ({
|
container: (base: any): any => ({
|
||||||
...base,
|
...base,
|
||||||
margin: "0 2px",
|
margin: "0 2px",
|
||||||
|
@ -72,15 +73,15 @@ const SelectorStyle: any = {
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
border: "1px solid",
|
border: "1px solid",
|
||||||
borderColor: selectedTheme.mainColor
|
borderColor: selectedTheme.mainColor,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
dropdownIndicator: (base: any): any => ({
|
dropdownIndicator: (base: any): any => ({
|
||||||
...base,
|
...base,
|
||||||
color: selectedTheme.mainColor,
|
color: selectedTheme.mainColor,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: selectedTheme.mainColor
|
color: selectedTheme.mainColor,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: () => ({
|
indicatorSeparator: () => ({
|
||||||
display: "none",
|
display: "none",
|
||||||
|
@ -91,7 +92,7 @@ const SelectorStyle: any = {
|
||||||
border: "1px solid " + selectedTheme.mainColor,
|
border: "1px solid " + selectedTheme.mainColor,
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
margin: "4px 0"
|
margin: "4px 0",
|
||||||
}),
|
}),
|
||||||
option: (base: any): any => ({
|
option: (base: any): any => ({
|
||||||
...base,
|
...base,
|
||||||
|
@ -125,7 +126,7 @@ interface ISettingsProps {
|
||||||
const Settings = ({ themes, search }: ISettingsProps) => {
|
const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
const [newTheme, setNewTheme] = useState<IThemeProps>();
|
const [newTheme, setNewTheme] = useState<IThemeProps>();
|
||||||
|
|
||||||
if (themes && search) {
|
if (themes || search) {
|
||||||
return (
|
return (
|
||||||
<Modal element="icon" icon="settings" title="Settings">
|
<Modal element="icon" icon="settings" title="Settings">
|
||||||
{themes && (
|
{themes && (
|
||||||
|
@ -133,6 +134,7 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
<SectionHeadline>Theme:</SectionHeadline>
|
<SectionHeadline>Theme:</SectionHeadline>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<Select
|
<Select
|
||||||
|
classNamePrefix="list"
|
||||||
options={themes}
|
options={themes}
|
||||||
defaultValue={selectedTheme}
|
defaultValue={selectedTheme}
|
||||||
onChange={(e: ValueType<IThemeProps, false>) => {
|
onChange={(e: ValueType<IThemeProps, false>) => {
|
||||||
|
@ -140,10 +142,20 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
}}
|
}}
|
||||||
styles={SelectorStyle}
|
styles={SelectorStyle}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => setTheme(JSON.stringify(newTheme))}>
|
<Button
|
||||||
|
data-testid="button-submit"
|
||||||
|
onClick={() => {
|
||||||
|
if (newTheme) setTheme(newTheme);
|
||||||
|
}}
|
||||||
|
>
|
||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => window.location.reload()}>Refresh</Button>
|
<Button
|
||||||
|
data-testid="button-refresh"
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
|
@ -155,24 +167,22 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
<Code>{search.defaultProvider}</Code>
|
<Code>{search.defaultProvider}</Code>
|
||||||
</>
|
</>
|
||||||
<>
|
<>
|
||||||
{
|
{search.providers && (
|
||||||
search.providers && (
|
<Table>
|
||||||
<Table>
|
<tbody>
|
||||||
<tbody>
|
<TableRow>
|
||||||
<TableRow>
|
<HeadCell>Search Provider</HeadCell>
|
||||||
<HeadCell>Search Provider</HeadCell>
|
<HeadCell>Prefix</HeadCell>
|
||||||
<HeadCell>Prefix</HeadCell>
|
</TableRow>
|
||||||
|
{search.providers.map((provider, index) => (
|
||||||
|
<TableRow key={provider.name + index}>
|
||||||
|
<TableCell>{provider.name}</TableCell>
|
||||||
|
<TableCell>{provider.prefix}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{search.providers.map((provider, index) => (
|
))}
|
||||||
<TableRow key={provider.name + index}>
|
</tbody>
|
||||||
<TableCell>{provider.name}</TableCell>
|
</Table>
|
||||||
<TableCell>{provider.prefix}</TableCell>
|
)}
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</Table>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -7,7 +7,7 @@ ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById("root")
|
document.getElementById("root"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
|
32
src/lib/fetcher.d.ts
vendored
Normal file
32
src/lib/fetcher.d.ts
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { ISearchProps } from "../components/searchBar";
|
||||||
|
import { IBookmarkGroupProps } from "../components/bookmarkGroup";
|
||||||
|
import { IAppCategoryProps } from "../components/appCategory";
|
||||||
|
import { IAppProps } from "../components/app";
|
||||||
|
import { IThemeProps } from "./theme";
|
||||||
|
import { IImprintProps } from "../components/imprint";
|
||||||
|
import { IGreeterProps } from "../components/greeter";
|
||||||
|
|
||||||
|
declare module "../data/apps.json" {
|
||||||
|
export const categories: IAppCategoryProps[];
|
||||||
|
export const apps: IAppProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "../data/search.json" {
|
||||||
|
export const search: ISearchProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "../data/bookmarks.json" {
|
||||||
|
export const groups: IBookmarkGroupProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "../data/themes.json" {
|
||||||
|
export const themes: IThemeProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "../data/imprint.json" {
|
||||||
|
export const imprint: IImprintProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "../data/greeter.json" {
|
||||||
|
export const greeter: IGreeterProps;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ISearchProps } from "../components/searchBar";
|
import { ISearchProps } from "../components/searchBar";
|
||||||
import { IBookmarkGroupProps } from "../components/bookmarkGroup";
|
import { IBookmarkGroupProps } from "../components/bookmarks";
|
||||||
import { IAppCategoryProps } from "../components/appCategory";
|
import { IAppCategoryProps } from "../components/appCategory";
|
||||||
import { IAppProps } from "../components/app";
|
import { IAppProps } from "../components/app";
|
||||||
import { IThemeProps } from "./theme";
|
import { IThemeProps } from "./theme";
|
||||||
|
@ -17,7 +17,7 @@ const inProduction = process.env.NODE_ENV === "production";
|
||||||
* @returns - The response in JSON
|
* @returns - The response in JSON
|
||||||
* @throws - Error with given error message if request failed
|
* @throws - Error with given error message if request failed
|
||||||
*/
|
*/
|
||||||
const handleResponse = (response: Response) => {
|
export const handleResponse = (response: Response) => {
|
||||||
if (response.ok) return response.json();
|
if (response.ok) return response.json();
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,7 @@ export interface IGreeterDataProps {
|
||||||
/**
|
/**
|
||||||
* Default values for the respective state variables
|
* Default values for the respective state variables
|
||||||
*/
|
*/
|
||||||
const defaults = {
|
export const defaults = {
|
||||||
app: {
|
app: {
|
||||||
categories: [],
|
categories: [],
|
||||||
apps: [],
|
apps: [],
|
||||||
|
@ -102,7 +102,7 @@ const defaults = {
|
||||||
"September",
|
"September",
|
||||||
"October",
|
"October",
|
||||||
"November",
|
"November",
|
||||||
"December"
|
"December",
|
||||||
],
|
],
|
||||||
days: [
|
days: [
|
||||||
"Sunday",
|
"Sunday",
|
||||||
|
@ -111,34 +111,34 @@ const defaults = {
|
||||||
"Wednesday",
|
"Wednesday",
|
||||||
"Thursday",
|
"Thursday",
|
||||||
"Friday",
|
"Friday",
|
||||||
"Saturday"
|
"Saturday",
|
||||||
],
|
],
|
||||||
greetings: [
|
greetings: [
|
||||||
{
|
{
|
||||||
greeting: "Good night!",
|
greeting: "Good night!",
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 6
|
end: 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
greeting: "Good morning!",
|
greeting: "Good morning!",
|
||||||
start: 6,
|
start: 6,
|
||||||
end: 12
|
end: 12,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
greeting: "Good afternoon!",
|
greeting: "Good afternoon!",
|
||||||
start: 12,
|
start: 12,
|
||||||
end: 18
|
end: 18,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
greeting: "Good evening!",
|
greeting: "Good evening!",
|
||||||
start: 18,
|
start: 18,
|
||||||
end: 0
|
end: 0,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
dateformat: "%wd, %m %d%e %y"
|
dateformat: "%wd, %m %d%e %y",
|
||||||
},
|
},
|
||||||
error: false,
|
error: false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,47 +146,59 @@ const defaults = {
|
||||||
* @param {string} type - The type of fetch request that threw an error
|
* @param {string} type - The type of fetch request that threw an error
|
||||||
* @param {Error} error - The error itself
|
* @param {Error} error - The error itself
|
||||||
*/
|
*/
|
||||||
const handleError = (status: string, error: Error) => {
|
export const handleError = (status: string, error: Error) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "apps":
|
case "apps":
|
||||||
return { ...defaults.app, error: error.message }
|
return { ...defaults.app, error: error.message };
|
||||||
case "bookmark":
|
case "bookmark":
|
||||||
return { ...defaults.bookmark, error: error.message }
|
return { ...defaults.bookmark, error: error.message };
|
||||||
case "searchProvider":
|
case "searchProvider":
|
||||||
return { ...defaults.search, error: error.message }
|
return { ...defaults.search, error: error.message };
|
||||||
case "theme":
|
case "theme":
|
||||||
return { ...defaults.theme, error: error.message }
|
return { ...defaults.theme, error: error.message };
|
||||||
case "imprint":
|
case "imprint":
|
||||||
return { ...defaults.imprint, error: error.message }
|
return { ...defaults.imprint, error: error.message };
|
||||||
case "greeter":
|
case "greeter":
|
||||||
return { ...defaults.greeter, error: error.message }
|
return { ...defaults.greeter, error: error.message };
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all of the data by doing fetch requests (only available in production)
|
* Fetches all of the data by doing fetch requests (only available in production)
|
||||||
*/
|
*/
|
||||||
const fetchProduction = Promise.all([
|
export const fetchProduction = Promise.all([
|
||||||
fetch("/data/apps.json").then(handleResponse).catch((error: Error) => handleError("apps", error)),
|
fetch("/data/apps.json")
|
||||||
fetch("/data/bookmarks.json").then(handleResponse).catch((error: Error) => handleError("bookmark", error)),
|
.then(handleResponse)
|
||||||
fetch("/data/search.json").then(handleResponse).catch((error: Error) => handleError("searchProvider", error)),
|
.catch((error: Error) => handleError("apps", error)),
|
||||||
fetch("/data/themes.json").then(handleResponse).catch((error: Error) => handleError("theme", error)),
|
fetch("/data/bookmarks.json")
|
||||||
fetch("/data/imprint.json").then(handleResponse).catch((error: Error) => handleError("imprint", error)),
|
.then(handleResponse)
|
||||||
fetch("/data/greeter.json").then(handleResponse).catch((error: Error) => handleError("greeter", error))
|
.catch((error: Error) => handleError("bookmark", error)),
|
||||||
|
fetch("/data/search.json")
|
||||||
|
.then(handleResponse)
|
||||||
|
.catch((error: Error) => handleError("searchProvider", error)),
|
||||||
|
fetch("/data/themes.json")
|
||||||
|
.then(handleResponse)
|
||||||
|
.catch((error: Error) => handleError("theme", error)),
|
||||||
|
fetch("/data/imprint.json")
|
||||||
|
.then(handleResponse)
|
||||||
|
.catch((error: Error) => handleError("imprint", error)),
|
||||||
|
fetch("/data/greeter.json")
|
||||||
|
.then(handleResponse)
|
||||||
|
.catch((error: Error) => handleError("greeter", error)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all of the data by importing it (only available in development)
|
* Fetches all of the data by importing it (only available in development)
|
||||||
*/
|
*/
|
||||||
const fetchDevelopment = Promise.all([
|
export const fetchDevelopment = Promise.all([
|
||||||
import("../data/apps.json"),
|
import("../data/apps.json"),
|
||||||
import("../data/bookmarks.json"),
|
import("../data/bookmarks.json"),
|
||||||
import("../data/search.json"),
|
import("../data/search.json"),
|
||||||
import("../data/themes.json"),
|
import("../data/themes.json"),
|
||||||
import("../data/imprint.json"),
|
import("../data/imprint.json"),
|
||||||
import("../data/greeter.json")
|
import("../data/greeter.json"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,32 +208,56 @@ export const useFetcher = () => {
|
||||||
const [appData, setAppData] = useState<IAppDataProps>(defaults.app);
|
const [appData, setAppData] = useState<IAppDataProps>(defaults.app);
|
||||||
|
|
||||||
const [bookmarkData, setBookmarkData] = useState<IBookmarkDataProps>(
|
const [bookmarkData, setBookmarkData] = useState<IBookmarkDataProps>(
|
||||||
defaults.bookmark
|
defaults.bookmark,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [
|
const [searchProviderData, setSearchProviderData] =
|
||||||
searchProviderData,
|
useState<ISearchDataProps>(defaults.search);
|
||||||
setSearchProviderData,
|
|
||||||
] = useState<ISearchDataProps>(defaults.search);
|
|
||||||
|
|
||||||
const [themeData, setThemeData] = useState<IThemeDataProps>(defaults.theme);
|
const [themeData, setThemeData] = useState<IThemeDataProps>(defaults.theme);
|
||||||
|
|
||||||
const [imprintData, setImprintData] = useState<IImprintDataProps>(
|
const [imprintData, setImprintData] = useState<IImprintDataProps>(
|
||||||
defaults.imprint
|
defaults.imprint,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [greeterData, setGreeterData] = useState<IGreeterDataProps>(defaults.greeter);
|
const [greeterData, setGreeterData] = useState<IGreeterDataProps>(
|
||||||
|
defaults.greeter,
|
||||||
|
);
|
||||||
|
|
||||||
const callback = useCallback(() => {
|
const callback = useCallback(() => {
|
||||||
(inProduction ? fetchProduction : fetchDevelopment).then(
|
(inProduction ? fetchProduction : fetchDevelopment).then(
|
||||||
([appData, bookmarkData, searchData, themeData, imprintData, greeterData]: [IAppDataProps, IBookmarkDataProps, ISearchDataProps, IThemeDataProps, IImprintDataProps, IGreeterDataProps]) => {
|
([
|
||||||
setAppData((appData.error) ? appData : { ...appData, error: false });
|
appData,
|
||||||
setBookmarkData((bookmarkData.error) ? bookmarkData : { ...bookmarkData, error: false });
|
bookmarkData,
|
||||||
setSearchProviderData((searchData.error) ? searchData : { ...searchData, error: false });
|
searchData,
|
||||||
setThemeData((themeData.error) ? themeData : { ...themeData, error: false });
|
themeData,
|
||||||
setImprintData((imprintData.error) ? imprintData : { ...imprintData, error: false });
|
imprintData,
|
||||||
setGreeterData((greeterData.error) ? greeterData : { ...greeterData, error: false });
|
greeterData,
|
||||||
}
|
]: [
|
||||||
|
IAppDataProps,
|
||||||
|
IBookmarkDataProps,
|
||||||
|
ISearchDataProps,
|
||||||
|
IThemeDataProps,
|
||||||
|
IImprintDataProps,
|
||||||
|
IGreeterDataProps,
|
||||||
|
]) => {
|
||||||
|
setAppData(appData.error ? appData : { ...appData, error: false });
|
||||||
|
setBookmarkData(
|
||||||
|
bookmarkData.error ? bookmarkData : { ...bookmarkData, error: false },
|
||||||
|
);
|
||||||
|
setSearchProviderData(
|
||||||
|
searchData.error ? searchData : { ...searchData, error: false },
|
||||||
|
);
|
||||||
|
setThemeData(
|
||||||
|
themeData.error ? themeData : { ...themeData, error: false },
|
||||||
|
);
|
||||||
|
setImprintData(
|
||||||
|
imprintData.error ? imprintData : { ...imprintData, error: false },
|
||||||
|
);
|
||||||
|
setGreeterData(
|
||||||
|
greeterData.error ? greeterData : { ...greeterData, error: false },
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -234,7 +270,7 @@ export const useFetcher = () => {
|
||||||
themeData,
|
themeData,
|
||||||
imprintData,
|
imprintData,
|
||||||
greeterData,
|
greeterData,
|
||||||
callback
|
callback,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ export interface IThemeProps {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTheme: IThemeProps = {
|
export const defaultTheme: IThemeProps = {
|
||||||
label: "Classic",
|
label: "Classic",
|
||||||
value: 0,
|
value: 0,
|
||||||
mainColor: "#000000",
|
mainColor: "#000000",
|
||||||
|
@ -18,8 +18,8 @@ const defaultTheme: IThemeProps = {
|
||||||
* Writes a given theme into localStorage
|
* Writes a given theme into localStorage
|
||||||
* @param {string} theme - the theme that shall be saved (in stringified JSON)
|
* @param {string} theme - the theme that shall be saved (in stringified JSON)
|
||||||
*/
|
*/
|
||||||
export const setTheme = (theme: string) => {
|
export const setTheme = (theme: IThemeProps) => {
|
||||||
if (theme !== undefined) localStorage.setItem("theme", theme);
|
localStorage.setItem("theme", JSON.stringify(theme));
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,12 +27,11 @@ export const setTheme = (theme: string) => {
|
||||||
* Function that gets the saved theme from localStorage or returns the default
|
* Function that gets the saved theme from localStorage or returns the default
|
||||||
* @returns {IThemeProps} the saved theme or the default theme
|
* @returns {IThemeProps} the saved theme or the default theme
|
||||||
*/
|
*/
|
||||||
const getTheme = (): IThemeProps => {
|
export const getTheme = (): IThemeProps => {
|
||||||
let selectedTheme = defaultTheme;
|
let selectedTheme = defaultTheme;
|
||||||
|
|
||||||
if (localStorage.getItem("theme") !== null) {
|
let theme = localStorage.getItem("theme");
|
||||||
selectedTheme = JSON.parse(localStorage.getItem("theme") || "{}");
|
if (theme !== null) selectedTheme = JSON.parse(theme || "{}");
|
||||||
}
|
|
||||||
|
|
||||||
return selectedTheme;
|
return selectedTheme;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,13 +11,13 @@
|
||||||
// opt-in, read https://bit.ly/CRA-PWA
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
const isLocalhost = Boolean(
|
||||||
window.location.hostname === 'localhost' ||
|
window.location.hostname === "localhost" ||
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === "[::1]" ||
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
|
@ -26,12 +26,9 @@ type Config = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function register(config?: Config) {
|
export function register(config?: Config) {
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
process.env.PUBLIC_URL,
|
|
||||||
window.location.href
|
|
||||||
);
|
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
@ -39,7 +36,7 @@ export function register(config?: Config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener("load", () => {
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
if (isLocalhost) {
|
if (isLocalhost) {
|
||||||
|
@ -50,8 +47,8 @@ export function register(config?: Config) {
|
||||||
// service worker/PWA documentation.
|
// service worker/PWA documentation.
|
||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'This web app is being served cache-first by a service ' +
|
"This web app is being served cache-first by a service " +
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
"worker. To learn more, visit https://bit.ly/CRA-PWA",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,21 +62,21 @@ export function register(config?: Config) {
|
||||||
function registerValidSW(swUrl: string, config?: Config) {
|
function registerValidSW(swUrl: string, config?: Config) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then(registration => {
|
.then((registration) => {
|
||||||
registration.onupdatefound = () => {
|
registration.onupdatefound = () => {
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing;
|
||||||
if (installingWorker == null) {
|
if (installingWorker == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
installingWorker.onstatechange = () => {
|
installingWorker.onstatechange = () => {
|
||||||
if (installingWorker.state === 'installed') {
|
if (installingWorker.state === "installed") {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
// At this point, the updated precached content has been fetched,
|
// At this point, the updated precached content has been fetched,
|
||||||
// but the previous service worker will still serve the older
|
// but the previous service worker will still serve the older
|
||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
'New content is available and will be used when all ' +
|
"New content is available and will be used when all " +
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
"tabs for this page are closed. See https://bit.ly/CRA-PWA.",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
|
@ -90,7 +87,7 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "Content is cached for offline use." message.
|
||||||
console.log('Content is cached for offline use.');
|
console.log("Content is cached for offline use.");
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onSuccess) {
|
if (config && config.onSuccess) {
|
||||||
|
@ -101,25 +98,25 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error('Error during service worker registration:', error);
|
console.error("Error during service worker registration:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { 'Service-Worker': 'script' }
|
headers: { "Service-Worker": "script" },
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get("content-type");
|
||||||
if (
|
if (
|
||||||
response.status === 404 ||
|
response.status === 404 ||
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
(contentType != null && contentType.indexOf("javascript") === -1)
|
||||||
) {
|
) {
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
registration.unregister().then(() => {
|
registration.unregister().then(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
@ -131,18 +128,18 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'No internet connection found. App is running in offline mode.'
|
"No internet connection found. App is running in offline mode.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregister() {
|
export function unregister() {
|
||||||
if ('serviceWorker' in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker.ready
|
||||||
.then(registration => {
|
.then((registration) => {
|
||||||
registration.unregister();
|
registration.unregister();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
// allows you to do things like:
|
// allows you to do things like:
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
|
|
3
src/test/components/__snapshots__/app.spec.tsx.snap
Normal file
3
src/test/components/__snapshots__/app.spec.tsx.snap
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`App snapshot test 1`] = `[Function]`;
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AppCategory snapshot test 1`] = `[Function]`;
|
3
src/test/components/__snapshots__/appList.spec.tsx.snap
Normal file
3
src/test/components/__snapshots__/appList.spec.tsx.snap
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AppList snapshot test 1`] = `[Function]`;
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`BookmarkGroup snapshot test 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`BookmarkList snapshot test 1`] = `[Function]`;
|
9
src/test/components/__snapshots__/elements.spec.tsx.snap
Normal file
9
src/test/components/__snapshots__/elements.spec.tsx.snap
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Tests Button 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`Tests Headline 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`Tests Lists 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`Tests SubHeadline 1`] = `[Function]`;
|
3
src/test/components/__snapshots__/greeter.spec.tsx.snap
Normal file
3
src/test/components/__snapshots__/greeter.spec.tsx.snap
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Greeter snapshot test 1`] = `[Function]`;
|
7
src/test/components/__snapshots__/icon.spec.tsx.snap
Normal file
7
src/test/components/__snapshots__/icon.spec.tsx.snap
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Icon test (no size) 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`Icon test 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`IconButton test 1`] = `[Function]`;
|
5
src/test/components/__snapshots__/imprint.spec.tsx.snap
Normal file
5
src/test/components/__snapshots__/imprint.spec.tsx.snap
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`imprint.tsx Tests Imprint 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`imprint.tsx Tests ImprintField 1`] = `[Function]`;
|
9
src/test/components/__snapshots__/modal.spec.tsx.snap
Normal file
9
src/test/components/__snapshots__/modal.spec.tsx.snap
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`modal.tsx Tests modal with icon 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`modal.tsx Tests modal with icon button 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`modal.tsx Tests modal with neither 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`modal.tsx Tests modal with text button 1`] = `[Function]`;
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`searchBar.tsx Tests SearchBar rendering 1`] = `[Function]`;
|
15
src/test/components/__snapshots__/settings.spec.tsx.snap
Normal file
15
src/test/components/__snapshots__/settings.spec.tsx.snap
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests forms 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests sections 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests settings rendering 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests settings rendering 2`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests settings rendering 3`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests settings rendering 4`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`settings.tsx Tests tables 1`] = `[Function]`;
|
24
src/test/components/app.spec.tsx
Normal file
24
src/test/components/app.spec.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import App, { IAppProps } from "../../components/app";
|
||||||
|
|
||||||
|
const props: IAppProps = {
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
newTab: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
it("App snapshot test", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<App
|
||||||
|
name={props.name}
|
||||||
|
icon={props.icon}
|
||||||
|
url={props.url}
|
||||||
|
displayURL={props.displayURL}
|
||||||
|
newTab={props.newTab}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
30
src/test/components/appCategory.spec.tsx
Normal file
30
src/test/components/appCategory.spec.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import AppCategory, { IAppCategoryProps } from "../../components/appCategory";
|
||||||
|
|
||||||
|
const props: IAppCategoryProps = {
|
||||||
|
name: "Category Test",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
newTab: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
newTab: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
it("AppCategory snapshot test", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<AppCategory name={props.name} items={props.items} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
43
src/test/components/appList.spec.tsx
Normal file
43
src/test/components/appList.spec.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import AppList, { IAppListProps } from "../../components/appList";
|
||||||
|
|
||||||
|
const props: IAppListProps = {
|
||||||
|
categories: [
|
||||||
|
{
|
||||||
|
name: "Category Test",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
newTab: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
newTab: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
newTab: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
it("AppList snapshot test", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<AppList categories={props.categories} apps={props.apps} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
39
src/test/components/bookmarks.spec.tsx
Normal file
39
src/test/components/bookmarks.spec.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import BookmarkList, {
|
||||||
|
BookmarkGroup,
|
||||||
|
IBookmarkGroupProps,
|
||||||
|
IBookmarkListProps,
|
||||||
|
} from "../../components/bookmarks";
|
||||||
|
|
||||||
|
const bookmarkGroupProps: IBookmarkGroupProps = {
|
||||||
|
name: "Test Group",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: "Bookmark Test",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const bookmarkListProps: IBookmarkListProps = {
|
||||||
|
groups: [bookmarkGroupProps, bookmarkGroupProps],
|
||||||
|
};
|
||||||
|
|
||||||
|
it("BookmarkGroup snapshot test", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<BookmarkGroup
|
||||||
|
name={bookmarkGroupProps.name}
|
||||||
|
items={bookmarkGroupProps.items}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("BookmarkList snapshot test", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<BookmarkList groups={bookmarkListProps.groups} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
39
src/test/components/elements.spec.tsx
Normal file
39
src/test/components/elements.spec.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import {
|
||||||
|
ListContainer,
|
||||||
|
Headline,
|
||||||
|
SubHeadline,
|
||||||
|
ItemList,
|
||||||
|
Item,
|
||||||
|
Button,
|
||||||
|
} from "../../components/elements";
|
||||||
|
|
||||||
|
it("Tests Lists", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<ListContainer>
|
||||||
|
<ItemList>
|
||||||
|
<Item>Test</Item>
|
||||||
|
</ItemList>
|
||||||
|
</ListContainer>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests Headline", () => {
|
||||||
|
const { asFragment } = render(<Headline>Test</Headline>);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests SubHeadline", () => {
|
||||||
|
const { asFragment } = render(<SubHeadline>Test</SubHeadline>);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests Button", () => {
|
||||||
|
const { asFragment } = render(<Button>Test</Button>);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
77
src/test/components/greeter.spec.tsx
Normal file
77
src/test/components/greeter.spec.tsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import Greeter, {
|
||||||
|
IGreeterProps,
|
||||||
|
isBetween,
|
||||||
|
getExtension,
|
||||||
|
} from "../../components/greeter";
|
||||||
|
|
||||||
|
const props: IGreeterProps = {
|
||||||
|
months: [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
],
|
||||||
|
days: [
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
],
|
||||||
|
greetings: [
|
||||||
|
{
|
||||||
|
greeting: "Good night!",
|
||||||
|
start: 0,
|
||||||
|
end: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
greeting: "Good morning!",
|
||||||
|
start: 6,
|
||||||
|
end: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
greeting: "Good afternoon!",
|
||||||
|
start: 12,
|
||||||
|
end: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
greeting: "Good evening!",
|
||||||
|
start: 18,
|
||||||
|
end: 24,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dateformat: "%wd, %m %d%e %y",
|
||||||
|
};
|
||||||
|
|
||||||
|
it("isBetween test", () => {
|
||||||
|
expect(isBetween(5, 1, 3)).toBeFalsy;
|
||||||
|
expect(isBetween(64, 1, 8)).toBeFalsy;
|
||||||
|
expect(isBetween(-1, -5, -3)).toBeFalsy;
|
||||||
|
expect(isBetween(4, 4, 4)).toBeTruthy;
|
||||||
|
expect(isBetween(3, 1, 8)).toBeTruthy;
|
||||||
|
expect(isBetween(-3, -5, -1)).toBeTruthy;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getExtension test", () => {
|
||||||
|
expect(getExtension(1)).toEqual("st");
|
||||||
|
expect(getExtension(2)).toEqual("nd");
|
||||||
|
expect(getExtension(3)).toEqual("rd");
|
||||||
|
expect(getExtension(4)).toEqual("th");
|
||||||
|
expect(getExtension(55)).toEqual("th");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Greeter snapshot test", () => {
|
||||||
|
const { asFragment } = render(<Greeter data={props} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
30
src/test/components/icon.spec.tsx
Normal file
30
src/test/components/icon.spec.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import Icon, { IconButton } from "../../components/icon";
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
name: "bug_report",
|
||||||
|
size: "20px",
|
||||||
|
onClick: () => console.log("test"),
|
||||||
|
};
|
||||||
|
|
||||||
|
it("Icon test", () => {
|
||||||
|
const { asFragment } = render(<Icon name={props.name} size={props.size} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Icon test (no size)", () => {
|
||||||
|
const { asFragment } = render(<Icon name={props.name} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("IconButton test", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<IconButton icon={props.name} onClick={props.onClick} />,
|
||||||
|
);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
|
||||||
|
const handleClick = jest.fn();
|
||||||
|
render(<IconButton icon="question_answer" onClick={handleClick} />);
|
||||||
|
fireEvent.click(screen.getByText(/question_answer/i));
|
||||||
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
61
src/test/components/imprint.spec.tsx
Normal file
61
src/test/components/imprint.spec.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
import Imprint, { IImprintProps, ImprintField } from "../../components/imprint";
|
||||||
|
|
||||||
|
const props: IImprintProps = {
|
||||||
|
name: {
|
||||||
|
text: "Test Name",
|
||||||
|
link: "#",
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
text: "Test Address",
|
||||||
|
link: "#",
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
text: "Test Phone",
|
||||||
|
link: "#",
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
text: "Test Email",
|
||||||
|
link: "#",
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
text: "Test URL",
|
||||||
|
link: "#",
|
||||||
|
},
|
||||||
|
text: "This is a test!",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("imprint.tsx", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete global.window.location;
|
||||||
|
global.window = Object.create(window);
|
||||||
|
global.window.location = {
|
||||||
|
port: "123",
|
||||||
|
protocol: "http:",
|
||||||
|
hostname: "localhost",
|
||||||
|
href: "test",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests Imprint", () => {
|
||||||
|
const { asFragment } = render(<Imprint imprint={props} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests #imprint", () => {
|
||||||
|
const location = window.location.href;
|
||||||
|
window.location.href = location + "#imprint";
|
||||||
|
|
||||||
|
const imprintModal = render(<Imprint imprint={props} />);
|
||||||
|
|
||||||
|
fireEvent.click(imprintModal.getByTestId("toggle-button"));
|
||||||
|
//fireEvent.click(imprintModal.getByTestId("close-button"));
|
||||||
|
|
||||||
|
expect(window.location.href).toEqual(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests ImprintField", () => {
|
||||||
|
const { asFragment } = render(<ImprintField field={props.name} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
78
src/test/components/modal.spec.tsx
Normal file
78
src/test/components/modal.spec.tsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
import Modal, { IModalProps } from "../../components/modal";
|
||||||
|
|
||||||
|
const iconProps: IModalProps = {
|
||||||
|
element: "icon",
|
||||||
|
icon: "bug_report",
|
||||||
|
title: "Test Modal",
|
||||||
|
onClose: jest.fn(),
|
||||||
|
children: <p>Hello</p>,
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidIconProps: IModalProps = {
|
||||||
|
element: "icon",
|
||||||
|
title: "Test Modal",
|
||||||
|
onClose: jest.fn(),
|
||||||
|
children: <p>Hello</p>,
|
||||||
|
};
|
||||||
|
|
||||||
|
const textProps: IModalProps = {
|
||||||
|
element: "text",
|
||||||
|
text: "Test",
|
||||||
|
title: "Test Modal",
|
||||||
|
onClose: jest.fn(),
|
||||||
|
children: <p>Hello</p>,
|
||||||
|
};
|
||||||
|
|
||||||
|
const noneProps: IModalProps = {
|
||||||
|
element: "none",
|
||||||
|
title: "Test Modal",
|
||||||
|
children: <p>Hello</p>,
|
||||||
|
};
|
||||||
|
|
||||||
|
const setup = (props: IModalProps) => {
|
||||||
|
const modal = render(
|
||||||
|
<Modal
|
||||||
|
element={props.element}
|
||||||
|
icon={props.icon}
|
||||||
|
text={props.text}
|
||||||
|
title={props.title}
|
||||||
|
onClose={props.onClose}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Modal>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return modal;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("modal.tsx", () => {
|
||||||
|
it("Tests modal with icon button", () => {
|
||||||
|
const modal = setup(iconProps);
|
||||||
|
expect(modal.asFragment).toMatchSnapshot();
|
||||||
|
fireEvent.click(modal.getByTestId("toggle-button"));
|
||||||
|
fireEvent.click(modal.getByTestId("close-button"));
|
||||||
|
expect(iconProps.onClose).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests modal with text button", () => {
|
||||||
|
const modal = setup(textProps);
|
||||||
|
expect(modal.asFragment).toMatchSnapshot();
|
||||||
|
fireEvent.click(modal.getByTestId("toggle-button"));
|
||||||
|
fireEvent.click(modal.getByTestId("close-button"));
|
||||||
|
expect(textProps.onClose).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests modal with neither", () => {
|
||||||
|
const modal = setup(noneProps);
|
||||||
|
expect(modal.asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests modal with icon", () => {
|
||||||
|
const modal = setup(invalidIconProps);
|
||||||
|
expect(modal.asFragment).toMatchSnapshot();
|
||||||
|
fireEvent.click(modal.getByTestId("toggle-button"));
|
||||||
|
fireEvent.click(modal.getByTestId("close-button"));
|
||||||
|
expect(invalidIconProps.onClose).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
101
src/test/components/searchBar.spec.tsx
Normal file
101
src/test/components/searchBar.spec.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
import SearchBar, {
|
||||||
|
handleQueryWithProvider,
|
||||||
|
ISearchProviderProps,
|
||||||
|
ISearchBarProps,
|
||||||
|
} from "../../components/searchBar";
|
||||||
|
|
||||||
|
const providers: Array<ISearchProviderProps> = [
|
||||||
|
{
|
||||||
|
name: "Allmusic",
|
||||||
|
url: "https://www.allmusic.com/search/all/",
|
||||||
|
prefix: "/a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Discogs",
|
||||||
|
url: "https://www.discogs.com/search/?q=",
|
||||||
|
prefix: "/di",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duck Duck Go",
|
||||||
|
url: "https://duckduckgo.com/?q=",
|
||||||
|
prefix: "/d",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const searchBar = render(<SearchBar providers={providers} />);
|
||||||
|
const input = searchBar.getByTestId("search-input");
|
||||||
|
const clearButton = searchBar.getByTestId("search-clear");
|
||||||
|
const submitButton = searchBar.getByTestId("search-submit");
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchBar,
|
||||||
|
input,
|
||||||
|
clearButton,
|
||||||
|
submitButton,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("searchBar.tsx", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete global.window.location;
|
||||||
|
global.window = Object.create(window);
|
||||||
|
global.window.location = {
|
||||||
|
port: "123",
|
||||||
|
protocol: "http:",
|
||||||
|
hostname: "localhost",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests SearchBar rendering", () => {
|
||||||
|
const { asFragment } = render(<SearchBar providers={providers} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests handleQueryWithProvider", () => {
|
||||||
|
providers.forEach((provider: ISearchProviderProps) => {
|
||||||
|
handleQueryWithProvider(providers, provider.prefix + " test");
|
||||||
|
expect(window.location.href).toEqual(provider.url + "test");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests handleQueryWithProvider default", () => {
|
||||||
|
handleQueryWithProvider(providers, "test");
|
||||||
|
expect(window.location.href).toEqual("https://google.com/search?q=test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests handleQueryWithProvider without providers", () => {
|
||||||
|
handleQueryWithProvider(undefined, "test");
|
||||||
|
expect(window.location.href).toEqual("https://google.com/search?q=test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests SearchBar component with default search", () => {
|
||||||
|
const { input, submitButton } = setup();
|
||||||
|
fireEvent.change(input, { target: { value: "test" } });
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
expect(window.location.href).toEqual("https://google.com/search?q=test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests SearchBar component with other search", () => {
|
||||||
|
const { input, submitButton } = setup();
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "/a test" } });
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
expect(window.location.href).toEqual(
|
||||||
|
"https://www.allmusic.com/search/all/test",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests SearchBar component clear", () => {
|
||||||
|
const { input, clearButton, submitButton } = setup();
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "/a test" } });
|
||||||
|
fireEvent.click(clearButton);
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
expect(window.location.href).toEqual("https://google.com/search?q=");
|
||||||
|
});
|
||||||
|
});
|
143
src/test/components/settings.spec.tsx
Normal file
143
src/test/components/settings.spec.tsx
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
import Settings, {
|
||||||
|
FormContainer,
|
||||||
|
Table,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
HeadCell,
|
||||||
|
Section,
|
||||||
|
SectionHeadline,
|
||||||
|
SelectorStyle,
|
||||||
|
} from "../../components/settings";
|
||||||
|
import { ISearchProviderProps } from "../../components/searchBar";
|
||||||
|
import { IThemeProps } from "../../lib/theme";
|
||||||
|
|
||||||
|
const themes: Array<IThemeProps> = [
|
||||||
|
{
|
||||||
|
label: "Classic",
|
||||||
|
value: 0,
|
||||||
|
mainColor: "#000000",
|
||||||
|
accentColor: "#1e272e",
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Classic",
|
||||||
|
value: 0,
|
||||||
|
mainColor: "#000000",
|
||||||
|
accentColor: "#1e272e",
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const providers: Array<ISearchProviderProps> = [
|
||||||
|
{
|
||||||
|
name: "Allmusic",
|
||||||
|
url: "https://www.allmusic.com/search/all/",
|
||||||
|
prefix: "/a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Discogs",
|
||||||
|
url: "https://www.discogs.com/search/?q=",
|
||||||
|
prefix: "/di",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duck Duck Go",
|
||||||
|
url: "https://duckduckgo.com/?q=",
|
||||||
|
prefix: "/d",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const propsList = [
|
||||||
|
{
|
||||||
|
themes: themes,
|
||||||
|
providers: providers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
themes: themes,
|
||||||
|
providers: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
themes: undefined,
|
||||||
|
providers: providers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
themes: undefined,
|
||||||
|
providers: undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("settings.tsx", () => {
|
||||||
|
const location: Location = window.location;
|
||||||
|
8;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
delete window.location;
|
||||||
|
|
||||||
|
window.location = {
|
||||||
|
...location,
|
||||||
|
reload: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests forms", () => {
|
||||||
|
const { asFragment } = render(<FormContainer />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests tables", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<Table>
|
||||||
|
<tbody>
|
||||||
|
<TableRow>
|
||||||
|
<HeadCell>Test</HeadCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Test</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</tbody>
|
||||||
|
</Table>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests sections", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<Section>
|
||||||
|
<SectionHeadline>Test</SectionHeadline>
|
||||||
|
</Section>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests settings rendering", () => {
|
||||||
|
propsList.forEach((props) => {
|
||||||
|
const settings = render(
|
||||||
|
<Settings themes={props.themes} providers={props.providers} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(settings.asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Finish this test
|
||||||
|
it("Tests theme setting", () => {
|
||||||
|
const settings = render(
|
||||||
|
<Settings
|
||||||
|
themes={propsList[0].themes}
|
||||||
|
providers={propsList[0].providers}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleButton = settings.getByTestId("toggle-button");
|
||||||
|
|
||||||
|
const submitButton = settings.getByTestId("button-submit");
|
||||||
|
const refreshButton = settings.getByTestId("button-refresh");
|
||||||
|
|
||||||
|
fireEvent.click(toggleButton);
|
||||||
|
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
});
|
46
src/test/lib/fetcher.spec.tsx
Normal file
46
src/test/lib/fetcher.spec.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { ok } from "assert";
|
||||||
|
import useFetcher, {
|
||||||
|
defaults,
|
||||||
|
handleResponse,
|
||||||
|
handleError,
|
||||||
|
fetchProduction,
|
||||||
|
fetchDevelopment,
|
||||||
|
} from "../../lib/fetcher";
|
||||||
|
|
||||||
|
describe("fetcher.tsx", () => {
|
||||||
|
it("Tests handleResponse", () => {});
|
||||||
|
|
||||||
|
it("Tests handleError", () => {
|
||||||
|
expect(handleError("apps", Error("Test!"))).toEqual({
|
||||||
|
...defaults.app,
|
||||||
|
error: "Test!",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handleError("bookmark", Error("Test!"))).toEqual({
|
||||||
|
...defaults.bookmark,
|
||||||
|
error: "Test!",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handleError("searchProvider", Error("Test!"))).toEqual({
|
||||||
|
...defaults.search,
|
||||||
|
error: "Test!",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handleError("theme", Error("Test!"))).toEqual({
|
||||||
|
...defaults.theme,
|
||||||
|
error: "Test!",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handleError("imprint", Error("Test!"))).toEqual({
|
||||||
|
...defaults.imprint,
|
||||||
|
error: "Test!",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handleError("greeter", Error("Test!"))).toEqual({
|
||||||
|
...defaults.greeter,
|
||||||
|
error: "Test!",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handleError("", Error("Test!"))).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
54
src/test/lib/theme.spec.tsx
Normal file
54
src/test/lib/theme.spec.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { getTheme, IThemeProps, setTheme } from "../../lib/theme";
|
||||||
|
|
||||||
|
const props: IThemeProps = {
|
||||||
|
label: "Classic",
|
||||||
|
value: 0,
|
||||||
|
mainColor: "#000000",
|
||||||
|
accentColor: "#1e272e",
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
};
|
||||||
|
|
||||||
|
const location: Location = window.location;
|
||||||
|
const setup = () => {
|
||||||
|
Object.defineProperty(window, "localStorage", {
|
||||||
|
value: {
|
||||||
|
getItem: jest.fn(() => JSON.stringify(props)),
|
||||||
|
setItem: jest.fn(() => null),
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
delete window.location;
|
||||||
|
|
||||||
|
window.location = {
|
||||||
|
...location,
|
||||||
|
reload: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("theme.tsx", () => {
|
||||||
|
it("setTheme test", () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
setTheme(props);
|
||||||
|
expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
|
||||||
|
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||||
|
"theme",
|
||||||
|
JSON.stringify(props),
|
||||||
|
);
|
||||||
|
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests getTheme", () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
let themeTest = getTheme();
|
||||||
|
expect(themeTest).toEqual(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests getTheme with empty parameters", () => {
|
||||||
|
localStorage.setItem("theme", "");
|
||||||
|
expect(getTheme()).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,12 +1,8 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
"allowJs": false,
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
@ -20,7 +16,5 @@
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "src/test"]
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue