Added tests
This commit is contained in:
parent
753a55c9c1
commit
24e61efcf1
30 changed files with 2089 additions and 1737 deletions
57
.circleci/config.yml
Normal file
57
.circleci/config.yml
Normal file
|
@ -0,0 +1,57 @@
|
|||
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
|
||||
|
||||
frontend:
|
||||
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
|
||||
tilt:
|
||||
jobs:
|
||||
- style
|
34
.eslintrc.js
Normal file
34
.eslintrc.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
module.exports = {
|
||||
extends: ["eslint:recommended", "plugin:react-hooks/recommended"],
|
||||
rules: {
|
||||
orderedImports: true,
|
||||
completedDocs: [
|
||||
true,
|
||||
{
|
||||
enums: true,
|
||||
functions: {
|
||||
visibilities: ["exported"],
|
||||
},
|
||||
interfaces: {
|
||||
visibilities: ["exported"],
|
||||
},
|
||||
methods: {
|
||||
tags: {
|
||||
content: {},
|
||||
existence: ["inheritdoc", "override"],
|
||||
},
|
||||
},
|
||||
types: {
|
||||
visibilities: ["exported"],
|
||||
},
|
||||
variables: {
|
||||
visibilities: ["exported"],
|
||||
},
|
||||
},
|
||||
],
|
||||
maxClassesPerFile: false,
|
||||
maxLineLength: false,
|
||||
memberOrdering: false,
|
||||
variableName: false,
|
||||
},
|
||||
};
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
|||
|
||||
# testing
|
||||
/coverage
|
||||
__snapshots__
|
||||
|
||||
# building
|
||||
/build
|
||||
|
@ -21,3 +22,5 @@
|
|||
npm-debug.log*
|
||||
yarn-debug.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",
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
16
codecov.yml
Normal file
16
codecov.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default: off
|
||||
dashboard:
|
||||
target: 80%
|
||||
flags: dashboard
|
||||
|
||||
flags:
|
||||
dashboard:
|
||||
paths:
|
||||
- src/
|
||||
- data/
|
||||
|
||||
ignore:
|
||||
- node_modules
|
26
package.json
26
package.json
|
@ -8,14 +8,13 @@
|
|||
],
|
||||
"private": false,
|
||||
"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/react-dom": "^17.0.3",
|
||||
"@types/react-select": "^4.0.13",
|
||||
"@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-dom": "^17.0.2",
|
||||
"react-scripts": "^4.0.3",
|
||||
|
@ -23,11 +22,28 @@
|
|||
"styled-components": "^5.2.1",
|
||||
"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": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"coverage": "codecov -f coverage/*.json -F dashboard",
|
||||
"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": {
|
||||
"extends": "react-app"
|
||||
|
|
14
src/app.tsx
14
src/app.tsx
|
@ -3,7 +3,7 @@ import { createGlobalStyle } from "styled-components";
|
|||
import SearchBar from "./components/searchBar";
|
||||
import Greeter from "./components/greeter";
|
||||
import AppList from "./components/appList";
|
||||
import BookmarkList from "./components/bookmarkList";
|
||||
import BookmarkList from "./components/bookmarks";
|
||||
import Settings from "./components/settings";
|
||||
import Imprint from "./components/imprint";
|
||||
|
||||
|
@ -29,15 +29,21 @@ const GlobalStyle = createGlobalStyle`
|
|||
* Renders the entire app by calling individual components
|
||||
*/
|
||||
const App = () => {
|
||||
|
||||
const { appData, bookmarkData, searchProviderData, themeData, imprintData, greeterData } = useFetcher();
|
||||
const {
|
||||
appData,
|
||||
bookmarkData,
|
||||
searchProviderData,
|
||||
themeData,
|
||||
imprintData,
|
||||
greeterData,
|
||||
} = useFetcher();
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<div>
|
||||
<SearchBar providers={searchProviderData?.providers} />
|
||||
{!themeData.error && !searchProviderData.error && (
|
||||
{(!themeData.error || !searchProviderData.error) && (
|
||||
<Settings
|
||||
themes={themeData?.themes}
|
||||
providers={searchProviderData?.providers}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import Icon from "./icon";
|
||||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
|
@ -51,16 +51,17 @@ export interface IAppProps {
|
|||
|
||||
/**
|
||||
* 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) => {
|
||||
|
||||
useEffect(() => { console.log(newTab) }, [newTab])
|
||||
|
||||
const linkAttrs = (newTab !== undefined && newTab) ? {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
} : {};
|
||||
const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
|
||||
const linkAttrs =
|
||||
newTab !== undefined && newTab
|
||||
? {
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
<AppContainer href={url} {...linkAttrs}>
|
||||
|
@ -73,4 +74,6 @@ export const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
|
|||
</DetailsContainer>
|
||||
</AppContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { App, IAppProps } from "./app";
|
||||
import App, { IAppProps } from "./app";
|
||||
import { ItemList, Item, SubHeadline } from "./elements";
|
||||
|
||||
const CategoryHeadline = styled(SubHeadline)`
|
||||
|
@ -18,9 +17,10 @@ export interface IAppCategoryProps {
|
|||
|
||||
/**
|
||||
* 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>
|
||||
{name && <CategoryHeadline>{name}</CategoryHeadline>}
|
||||
<ItemList>
|
||||
|
@ -38,3 +38,5 @@ export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
|||
</ItemList>
|
||||
</CategoryContainer>
|
||||
);
|
||||
|
||||
export default AppCategory;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppCategory, IAppCategoryProps } from "./appCategory";
|
||||
import AppCategory, { IAppCategoryProps } from "./appCategory";
|
||||
import { IAppProps } from "./app";
|
||||
|
||||
import { Headline, ListContainer } from "./elements";
|
||||
|
@ -10,7 +10,8 @@ export interface IAppListProps {
|
|||
|
||||
/**
|
||||
* 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) => (
|
||||
<ListContainer>
|
||||
|
@ -20,10 +21,7 @@ const AppList = ({ categories, apps }: IAppListProps) => (
|
|||
<AppCategory key={[name, idx].join("")} name={name} items={items} />
|
||||
))}
|
||||
{apps && (
|
||||
<AppCategory
|
||||
name={categories ? "Uncategorized apps" : ""}
|
||||
items={apps}
|
||||
/>
|
||||
<AppCategory name={categories ? "Uncategorized apps" : ""} items={apps} />
|
||||
)}
|
||||
</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 { Item, SubHeadline } from "./elements";
|
||||
import {
|
||||
Headline,
|
||||
Item,
|
||||
ItemList,
|
||||
ListContainer,
|
||||
SubHeadline,
|
||||
} from "./elements";
|
||||
import selectedTheme from "../lib/theme";
|
||||
|
||||
const GroupContainer = styled.div`
|
||||
|
@ -32,9 +37,14 @@ export interface IBookmarkGroupProps {
|
|||
items: Array<IBookmarkProps>;
|
||||
}
|
||||
|
||||
export interface IBookmarkListProps {
|
||||
groups: Array<IBookmarkGroupProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => (
|
||||
<Item>
|
||||
|
@ -48,3 +58,21 @@ export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
|||
</GroupContainer>
|
||||
</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 selectedTheme from "../lib/theme";
|
||||
import Icon from "./icon";
|
||||
|
||||
export const ListContainer = styled.div`
|
||||
padding: 2rem 0;
|
||||
|
@ -56,29 +54,3 @@ export const Button = styled.button`
|
|||
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>
|
||||
);
|
||||
|
|
|
@ -38,17 +38,18 @@ interface IGreeterComponentProps {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param a the number that's supposed to be checked
|
||||
* @param b the minimum
|
||||
* @param c the maximum
|
||||
* Checks if a number is between two numbers
|
||||
* @param {number} a number that's supposed to be checked
|
||||
* @param {number} b minimum
|
||||
* @param {number} c maximum
|
||||
*/
|
||||
const isBetween = (a: number, b: number, c: number): boolean =>
|
||||
a >= b && a <= c;
|
||||
|
||||
/**
|
||||
* 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 => {
|
||||
let hours = Math.floor(new Date().getHours());
|
||||
|
@ -64,8 +65,8 @@ const getGreeting = (greetings: Array<IGreetingProps>): string => {
|
|||
|
||||
/**
|
||||
* 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
|
||||
* @returns {string} - The extension for that number
|
||||
* @param {number} day number of a day within a month
|
||||
* @returns {string} extension for that number
|
||||
*/
|
||||
const getExtension = (day: number) => {
|
||||
let extension = "";
|
||||
|
@ -85,13 +86,13 @@ const getExtension = (day: number) => {
|
|||
|
||||
/**
|
||||
* Generates the current date
|
||||
* @param {string} format - The format of the date string
|
||||
* @returns {string} - The current date as a string
|
||||
* @param {string} format format of the date string
|
||||
* @returns {string} current date as a string
|
||||
*/
|
||||
const getDateString = (
|
||||
weekdays: Array<string>,
|
||||
months: Array<string>,
|
||||
format: string
|
||||
format: string,
|
||||
) => {
|
||||
let currentDate = new Date();
|
||||
|
||||
|
@ -111,6 +112,8 @@ const getDateString = (
|
|||
|
||||
/**
|
||||
* Renders the Greeter
|
||||
* @param {IGreeterComponentProps} data required greeter data
|
||||
* @returns {React.ReactNode} the greeter
|
||||
*/
|
||||
const Greeter = ({ data }: IGreeterComponentProps) => (
|
||||
<GreeterContainer>
|
||||
|
|
|
@ -7,32 +7,62 @@ interface IIconProps {
|
|||
size?: string;
|
||||
}
|
||||
|
||||
interface IIconButtonProps {
|
||||
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";
|
||||
`;
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
|
||||
let IconContainer = styled.i`
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
let Container = styled(IconContainer)`
|
||||
font-size: ${size ? size : "24px"};
|
||||
color: ${selectedTheme.mainColor};
|
||||
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";
|
||||
`;
|
||||
|
||||
return <IconContainer>{name}</IconContainer>;
|
||||
return <Container>{name}</Container>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a button with an icon
|
||||
* @param {IIconProps} props - The props of the given IconButton
|
||||
* @returns {React.ReactNode} the icon button node
|
||||
*/
|
||||
export const IconButton = ({ icon, onClick }: IIconButtonProps) => (
|
||||
<StyledButton onClick={onClick}>
|
||||
<Icon name={icon} />
|
||||
</StyledButton>
|
||||
);
|
||||
|
||||
export default Icon;
|
||||
|
|
|
@ -2,12 +2,7 @@ import React from "react";
|
|||
import Modal from "./modal";
|
||||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
import {
|
||||
ListContainer,
|
||||
ItemList,
|
||||
Headline,
|
||||
SubHeadline,
|
||||
} from "./elements";
|
||||
import { ListContainer, ItemList, Headline, SubHeadline } from "./elements";
|
||||
|
||||
const ModalSubHeadline = styled(SubHeadline)`
|
||||
display: block;
|
||||
|
@ -37,11 +32,6 @@ const ItemContainer = styled.div`
|
|||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
interface IImprintFieldProps {
|
||||
text: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface IImprintProps {
|
||||
name: IImprintFieldProps;
|
||||
address: IImprintFieldProps;
|
||||
|
@ -51,17 +41,23 @@ export interface IImprintProps {
|
|||
text: string;
|
||||
}
|
||||
|
||||
interface IImprintFieldComponentProps {
|
||||
field: IImprintFieldProps;
|
||||
}
|
||||
|
||||
interface IImprintComponentProps {
|
||||
imprint: IImprintProps;
|
||||
}
|
||||
|
||||
interface IImprintFieldComponentProps {
|
||||
field: IImprintFieldProps;
|
||||
}
|
||||
|
||||
interface IImprintFieldProps {
|
||||
text: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => (
|
||||
<Link href={field.link}>{field.text}</Link>
|
||||
|
@ -69,7 +65,8 @@ const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
|||
|
||||
/**
|
||||
* 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) => (
|
||||
<>
|
||||
|
@ -93,7 +90,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
|||
<div>
|
||||
<ModalSubHeadline>
|
||||
Information in accordance with section 5 TMG
|
||||
</ModalSubHeadline>
|
||||
</ModalSubHeadline>
|
||||
<>
|
||||
{imprint.name && <ImprintField field={imprint.name} />}
|
||||
{imprint.address && <ImprintField field={imprint.address} />}
|
||||
|
@ -103,9 +100,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
|||
</>
|
||||
</div>
|
||||
<div>
|
||||
<ModalSubHeadline>
|
||||
Imprint
|
||||
</ModalSubHeadline>
|
||||
<ModalSubHeadline>Imprint</ModalSubHeadline>
|
||||
{imprint.text && <Text>{imprint.text}</Text>}
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -2,7 +2,8 @@ import React, { useState } from "react";
|
|||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
|
||||
import { Headline, IconButton } from "./elements";
|
||||
import { Headline } from "./elements";
|
||||
import { IconButton } from "./icon";
|
||||
|
||||
const ModalContainer = styled.div`
|
||||
position: absolute;
|
||||
|
@ -49,9 +50,18 @@ interface IModalProps {
|
|||
|
||||
/**
|
||||
* 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 closeModal = () => {
|
||||
|
@ -65,9 +75,7 @@ const Modal = ({ element, icon, text, condition, title, onClose, children }: IMo
|
|||
<IconButton icon={icon ?? ""} onClick={() => closeModal()} />
|
||||
)}
|
||||
|
||||
{element === "text" && (
|
||||
<Text onClick={() => closeModal()}>{text}</Text>
|
||||
)}
|
||||
{element === "text" && <Text onClick={() => closeModal()}>{text}</Text>}
|
||||
|
||||
<ModalContainer hidden={modalHidden}>
|
||||
<TitleContainer>
|
||||
|
|
|
@ -16,18 +16,17 @@ const Search = styled.form`
|
|||
|
||||
const SearchInput = styled.input`
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
|
||||
font-size: 1rem;
|
||||
|
||||
border: none;
|
||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
||||
border-radius: 0;
|
||||
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
color: ${selectedTheme.mainColor};
|
||||
|
||||
margin: 0px;
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
@ -50,7 +49,7 @@ interface ISearchBarProps {
|
|||
|
||||
/**
|
||||
* Renders a search bar
|
||||
* @param {ISearchBarProps} props - The search providers for the search bar to use
|
||||
* @param {ISearchBarProps} props - The search providers for the search bar to use
|
||||
*/
|
||||
const SearchBar = ({ providers }: ISearchBarProps) => {
|
||||
let [input, setInput] = useState<string>("");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Select, { ValueType } from "react-select";
|
||||
|
@ -62,15 +62,15 @@ const SelectorStyle: any = {
|
|||
boxShadow: "none",
|
||||
"&:hover": {
|
||||
border: "1px solid",
|
||||
borderColor: selectedTheme.mainColor
|
||||
borderColor: selectedTheme.mainColor,
|
||||
},
|
||||
}),
|
||||
dropdownIndicator: (base: any): any => ({
|
||||
...base,
|
||||
color: selectedTheme.mainColor,
|
||||
"&:hover": {
|
||||
color: selectedTheme.mainColor
|
||||
}
|
||||
color: selectedTheme.mainColor,
|
||||
},
|
||||
}),
|
||||
indicatorSeparator: () => ({
|
||||
display: "none",
|
||||
|
@ -81,7 +81,7 @@ const SelectorStyle: any = {
|
|||
border: "1px solid " + selectedTheme.mainColor,
|
||||
borderRadius: 0,
|
||||
boxShadow: "none",
|
||||
margin: "4px 0"
|
||||
margin: "4px 0",
|
||||
}),
|
||||
option: (base: any): any => ({
|
||||
...base,
|
||||
|
@ -115,7 +115,7 @@ interface ISettingsProps {
|
|||
const Settings = ({ themes, providers }: ISettingsProps) => {
|
||||
const [newTheme, setNewTheme] = useState<IThemeProps>();
|
||||
|
||||
if (themes && providers) {
|
||||
if (themes || providers) {
|
||||
return (
|
||||
<Modal element="icon" icon="settings" title="Settings">
|
||||
{themes && (
|
||||
|
|
|
@ -7,7 +7,7 @@ ReactDOM.render(
|
|||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
document.getElementById("root"),
|
||||
);
|
||||
|
||||
// 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 { ISearchProviderProps } from "../components/searchBar";
|
||||
import { IBookmarkGroupProps } from "../components/bookmarkGroup";
|
||||
import { IBookmarkGroupProps } from "../components/bookmarks";
|
||||
import { IAppCategoryProps } from "../components/appCategory";
|
||||
import { IAppProps } from "../components/app";
|
||||
import { IThemeProps } from "./theme";
|
||||
|
@ -99,7 +99,7 @@ const defaults = {
|
|||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
"December",
|
||||
],
|
||||
days: [
|
||||
"Sunday",
|
||||
|
@ -108,34 +108,34 @@ const defaults = {
|
|||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday"
|
||||
"Saturday",
|
||||
],
|
||||
greetings: [
|
||||
{
|
||||
greeting: "Good night!",
|
||||
start: 0,
|
||||
end: 6
|
||||
end: 6,
|
||||
},
|
||||
{
|
||||
greeting: "Good morning!",
|
||||
start: 6,
|
||||
end: 12
|
||||
end: 12,
|
||||
},
|
||||
{
|
||||
greeting: "Good afternoon!",
|
||||
start: 12,
|
||||
end: 18
|
||||
end: 18,
|
||||
},
|
||||
{
|
||||
greeting: "Good evening!",
|
||||
start: 18,
|
||||
end: 0
|
||||
}
|
||||
end: 0,
|
||||
},
|
||||
],
|
||||
dateformat: "%wd, %m %d%e %y"
|
||||
dateformat: "%wd, %m %d%e %y",
|
||||
},
|
||||
error: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -146,32 +146,44 @@ const defaults = {
|
|||
const handleError = (status: string, error: Error) => {
|
||||
switch (status) {
|
||||
case "apps":
|
||||
return { ...defaults.app, error: error.message }
|
||||
return { ...defaults.app, error: error.message };
|
||||
case "bookmark":
|
||||
return { ...defaults.bookmark, error: error.message }
|
||||
return { ...defaults.bookmark, error: error.message };
|
||||
case "searchProvider":
|
||||
return { ...defaults.search, error: error.message }
|
||||
return { ...defaults.search, error: error.message };
|
||||
case "theme":
|
||||
return { ...defaults.theme, error: error.message }
|
||||
return { ...defaults.theme, error: error.message };
|
||||
case "imprint":
|
||||
return { ...defaults.imprint, error: error.message }
|
||||
return { ...defaults.imprint, error: error.message };
|
||||
case "greeter":
|
||||
return { ...defaults.greeter, error: error.message }
|
||||
return { ...defaults.greeter, error: error.message };
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all of the data by doing fetch requests (only available in production)
|
||||
*/
|
||||
const fetchProduction = Promise.all([
|
||||
fetch("/data/apps.json").then(handleResponse).catch((error: Error) => handleError("apps", error)),
|
||||
fetch("/data/bookmarks.json").then(handleResponse).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))
|
||||
fetch("/data/apps.json")
|
||||
.then(handleResponse)
|
||||
.catch((error: Error) => handleError("apps", error)),
|
||||
fetch("/data/bookmarks.json")
|
||||
.then(handleResponse)
|
||||
.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)),
|
||||
]);
|
||||
|
||||
/**
|
||||
|
@ -183,7 +195,7 @@ const fetchDevelopment = Promise.all([
|
|||
import("../data/search.json"),
|
||||
import("../data/themes.json"),
|
||||
import("../data/imprint.json"),
|
||||
import("../data/greeter.json")
|
||||
import("../data/greeter.json"),
|
||||
]);
|
||||
|
||||
/**
|
||||
|
@ -193,32 +205,56 @@ export const useFetcher = () => {
|
|||
const [appData, setAppData] = useState<IAppDataProps>(defaults.app);
|
||||
|
||||
const [bookmarkData, setBookmarkData] = useState<IBookmarkDataProps>(
|
||||
defaults.bookmark
|
||||
defaults.bookmark,
|
||||
);
|
||||
|
||||
const [
|
||||
searchProviderData,
|
||||
setSearchProviderData,
|
||||
] = useState<ISearchProviderDataProps>(defaults.search);
|
||||
const [searchProviderData, setSearchProviderData] =
|
||||
useState<ISearchProviderDataProps>(defaults.search);
|
||||
|
||||
const [themeData, setThemeData] = useState<IThemeDataProps>(defaults.theme);
|
||||
|
||||
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(() => {
|
||||
(inProduction ? fetchProduction : fetchDevelopment).then(
|
||||
([appData, bookmarkData, searchData, themeData, imprintData, greeterData]: [IAppDataProps, IBookmarkDataProps, ISearchProviderDataProps, 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 });
|
||||
}
|
||||
([
|
||||
appData,
|
||||
bookmarkData,
|
||||
searchData,
|
||||
themeData,
|
||||
imprintData,
|
||||
greeterData,
|
||||
]: [
|
||||
IAppDataProps,
|
||||
IBookmarkDataProps,
|
||||
ISearchProviderDataProps,
|
||||
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 },
|
||||
);
|
||||
},
|
||||
);
|
||||
}, []);
|
||||
|
||||
|
@ -231,7 +267,7 @@ export const useFetcher = () => {
|
|||
themeData,
|
||||
imprintData,
|
||||
greeterData,
|
||||
callback
|
||||
callback,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@
|
|||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === "localhost" ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
window.location.hostname === "[::1]" ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
|
||||
),
|
||||
);
|
||||
|
||||
type Config = {
|
||||
|
@ -26,12 +26,9 @@ type 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.
|
||||
const publicUrl = new URL(
|
||||
process.env.PUBLIC_URL,
|
||||
window.location.href
|
||||
);
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
|
@ -39,7 +36,7 @@ export function register(config?: Config) {
|
|||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
window.addEventListener("load", () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
|
@ -50,8 +47,8 @@ export function register(config?: Config) {
|
|||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
"This web app is being served cache-first by a service " +
|
||||
"worker. To learn more, visit https://bit.ly/CRA-PWA",
|
||||
);
|
||||
});
|
||||
} else {
|
||||
|
@ -65,21 +62,21 @@ export function register(config?: Config) {
|
|||
function registerValidSW(swUrl: string, config?: Config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (installingWorker.state === "installed") {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
"New content is available and will be used when all " +
|
||||
"tabs for this page are closed. See https://bit.ly/CRA-PWA.",
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
|
@ -90,7 +87,7 @@ function registerValidSW(swUrl: string, config?: Config) {
|
|||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
console.log("Content is cached for offline use.");
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
|
@ -101,25 +98,25 @@ function registerValidSW(swUrl: string, config?: Config) {
|
|||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
.catch((error) => {
|
||||
console.error("Error during service worker registration:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
headers: { "Service-Worker": "script" },
|
||||
})
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
// 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 (
|
||||
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.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
|
@ -131,18 +128,18 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
|
|||
})
|
||||
.catch(() => {
|
||||
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() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then(registration => {
|
||||
.then((registration) => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
|
|
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("should take a snapshot", () => {
|
||||
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("should take a snapshot", () => {
|
||||
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("should take a snapshot", () => {
|
||||
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();
|
||||
});
|
|
@ -1,12 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
@ -20,7 +16,5 @@
|
|||
"jsx": "react-jsx",
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src", "src/test"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue