Rename files, update tests
This commit is contained in:
parent
786aca82f1
commit
ca2f7a763d
12 changed files with 409 additions and 6 deletions
|
@ -7,7 +7,7 @@ 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";
|
||||||
|
|
||||||
import { IThemeProps, getTheme, setScheme } from "./lib/theme";
|
import { IThemeProps, getTheme, setScheme } from "./lib/useTheme";
|
||||||
import useFetcher from "./lib/fetcher";
|
import useFetcher from "./lib/fetcher";
|
||||||
import useMediaQuery from "./lib/useMediaQuery";
|
import useMediaQuery from "./lib/useMediaQuery";
|
||||||
|
|
||||||
|
|
163
src/components/apps.tsx
Normal file
163
src/components/apps.tsx
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import Icon from "./icon";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import selectedTheme from "../lib/useTheme";
|
||||||
|
import {
|
||||||
|
Headline,
|
||||||
|
ListContainer,
|
||||||
|
ItemList,
|
||||||
|
Item,
|
||||||
|
SubHeadline,
|
||||||
|
} from "./elements";
|
||||||
|
|
||||||
|
const AppContainer = styled.a`
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
padding: 1rem;
|
||||||
|
color: ${selectedTheme.mainColor};
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const IconContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DetailsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AppName = styled.div`
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AppDescription = styled.p`
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: ${selectedTheme.accentColor};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CategoryHeadline = styled(SubHeadline)`
|
||||||
|
padding-top: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CategoryContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface IAppProps {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
url: string;
|
||||||
|
displayURL: string;
|
||||||
|
newTab?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppCategoryProps {
|
||||||
|
name: string;
|
||||||
|
items: Array<IAppProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppListProps {
|
||||||
|
categories?: Array<IAppCategoryProps>;
|
||||||
|
apps?: Array<IAppProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaults: IAppListProps = {
|
||||||
|
categories: [],
|
||||||
|
apps: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a single app shortcut
|
||||||
|
* @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 linkAttrs =
|
||||||
|
newTab !== undefined && newTab
|
||||||
|
? {
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noopener noreferrer",
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContainer href={url} {...linkAttrs}>
|
||||||
|
<IconContainer>
|
||||||
|
<Icon name={icon} />
|
||||||
|
</IconContainer>
|
||||||
|
<DetailsContainer>
|
||||||
|
<AppName>{name}</AppName>
|
||||||
|
<AppDescription>{displayURL}</AppDescription>
|
||||||
|
</DetailsContainer>
|
||||||
|
</AppContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders one app category
|
||||||
|
* @param {IAppCategoryProps} props props of the given category
|
||||||
|
* @returns {React.ReactNode} the app category node
|
||||||
|
*/
|
||||||
|
export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
||||||
|
<CategoryContainer>
|
||||||
|
{name && <CategoryHeadline>{name}</CategoryHeadline>}
|
||||||
|
<ItemList>
|
||||||
|
{items.map(({ name, icon, displayURL, newTab, url }, index) => (
|
||||||
|
<Item key={[name, index].join("")}>
|
||||||
|
<App
|
||||||
|
name={name}
|
||||||
|
icon={icon}
|
||||||
|
url={url}
|
||||||
|
displayURL={displayURL}
|
||||||
|
newTab={newTab}
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
|
))}
|
||||||
|
</ItemList>
|
||||||
|
</CategoryContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders one list containing all app categories and uncategorized apps
|
||||||
|
* @param {IAppListProps} props props of the given list of apps
|
||||||
|
* @returns {React.ReactNode} the app list component
|
||||||
|
*/
|
||||||
|
export const AppList = ({ categories, apps }: IAppListProps) => {
|
||||||
|
if (apps || categories) {
|
||||||
|
return (
|
||||||
|
<ListContainer>
|
||||||
|
<Headline>Applications</Headline>
|
||||||
|
{categories &&
|
||||||
|
categories.map(({ name, items }, index) => (
|
||||||
|
<AppCategory
|
||||||
|
key={[name, index].join("")}
|
||||||
|
name={name}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{apps && (
|
||||||
|
<AppCategory
|
||||||
|
name={categories ? "Uncategorized apps" : ""}
|
||||||
|
items={apps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ListContainer>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppList;
|
|
@ -4,7 +4,7 @@ import styled from "styled-components";
|
||||||
import Select from "./select";
|
import Select from "./select";
|
||||||
|
|
||||||
import { ISearchProps } from "./searchBar";
|
import { ISearchProps } from "./searchBar";
|
||||||
import { setTheme, IThemeProps, getTheme } from "../lib/theme";
|
import { setTheme, IThemeProps, getTheme } from "../lib/useTheme";
|
||||||
import { Button, SubHeadline } from "./elements";
|
import { Button, SubHeadline } from "./elements";
|
||||||
|
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
|
|
31
src/lib/fetch.d.ts
vendored
Normal file
31
src/lib/fetch.d.ts
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { ISearchProps } from "../components/searchBar";
|
||||||
|
import { IBookmarkGroupProps } from "../components/bookmarks";
|
||||||
|
import { IAppProps, IAppCategoryProps } from "../components/apps";
|
||||||
|
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;
|
||||||
|
}
|
91
src/lib/fetch.tsx
Normal file
91
src/lib/fetch.tsx
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { IAppListProps } from "../components/apps";
|
||||||
|
import { IThemeProps } from "./useTheme";
|
||||||
|
import { IBookmarkListProps } from "../components/bookmarks";
|
||||||
|
import { ISearchProps } from "../components/searchBar";
|
||||||
|
import { IImprintProps } from "../components/imprint";
|
||||||
|
import { IGreeterProps } from "../components/greeter";
|
||||||
|
|
||||||
|
const inProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
interface IFetchItemProps {
|
||||||
|
url: string;
|
||||||
|
setHook?: React.Dispatch<React.SetStateAction<any>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFetchListProps {
|
||||||
|
app: IFetchItemProps;
|
||||||
|
bookmarks: IFetchItemProps;
|
||||||
|
greeter: IFetchItemProps;
|
||||||
|
imprint: IFetchItemProps;
|
||||||
|
search: IFetchItemProps;
|
||||||
|
themes: IFetchItemProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fetchList: IFetchListProps = {
|
||||||
|
app: { url: "/data/app.json" },
|
||||||
|
bookmarks: { url: "/data/bookmarks.json" },
|
||||||
|
greeter: { url: "/data/greeter.json" },
|
||||||
|
imprint: { url: "/data/imprint.json" },
|
||||||
|
search: { url: "/data/search.json" },
|
||||||
|
themes: { url: "/data/themes.json" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleResponse = (response: Response, type: string) => {
|
||||||
|
if (response.ok) return response.json();
|
||||||
|
throw new Error("Error fetching " + type + " data");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error: Error) => {
|
||||||
|
console.error(error.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchURL = (url: string, type: string) => {
|
||||||
|
const response = inProduction ? fetch(url) : import(".." + url);
|
||||||
|
|
||||||
|
return response
|
||||||
|
.then((response: Response) => handleResponse(response, type))
|
||||||
|
.catch(handleError);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useFetch = () => {
|
||||||
|
const [appData, setAppData] = useState<IAppListProps>();
|
||||||
|
fetchList.app.setHook = setAppData;
|
||||||
|
|
||||||
|
const [bookmarkData, setBookmarkData] = useState<IBookmarkListProps>();
|
||||||
|
fetchList.bookmarks.setHook = setBookmarkData;
|
||||||
|
|
||||||
|
const [greeterData, setGreeterData] = useState<IGreeterProps>();
|
||||||
|
fetchList.greeter.setHook = setGreeterData;
|
||||||
|
|
||||||
|
const [imprintData, setImprintData] = useState<IImprintProps>();
|
||||||
|
fetchList.imprint.setHook = setImprintData;
|
||||||
|
|
||||||
|
const [searchData, setSearchData] = useState<ISearchProps>();
|
||||||
|
fetchList.search.setHook = setSearchData;
|
||||||
|
|
||||||
|
const [themeData, setThemeData] = useState<Array<IThemeProps>>();
|
||||||
|
fetchList.themes.setHook = setThemeData;
|
||||||
|
|
||||||
|
const callback = useCallback(() => {
|
||||||
|
Object.entries(fetchList).forEach(([key, val]) => {
|
||||||
|
fetchURL(val.url, key).then((data) => {
|
||||||
|
val.setHook(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => callback(), [callback]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
appData,
|
||||||
|
bookmarkData,
|
||||||
|
greeterData,
|
||||||
|
imprintData,
|
||||||
|
searchData,
|
||||||
|
themeData,
|
||||||
|
callback,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFetch;
|
|
@ -4,7 +4,7 @@ import { ISearchProps } from "../components/searchBar";
|
||||||
import { IBookmarkGroupProps } from "../components/bookmarks";
|
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 "./useTheme";
|
||||||
import { IImprintProps } from "../components/imprint";
|
import { IImprintProps } from "../components/imprint";
|
||||||
import { IGreeterProps } from "../components/greeter";
|
import { IGreeterProps } from "../components/greeter";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import App, { GlobalStyle } from "../app";
|
import App, { GlobalStyle } from "../app";
|
||||||
import { IThemeProps } from "../lib/theme";
|
import { IThemeProps } from "../lib/useTheme";
|
||||||
|
|
||||||
const props: IThemeProps = {
|
const props: IThemeProps = {
|
||||||
label: "Classic",
|
label: "Classic",
|
||||||
|
|
19
src/test/components/__snapshots__/apps.spec.tsx.snap
Normal file
19
src/test/components/__snapshots__/apps.spec.tsx.snap
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`app.tsx Tests App rendering with newTab 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests App rendering with newTab 2`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests App rendering without newTab 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests AppCategory rendering 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests AppCategory rendering 2`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests AppList rendering 1`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests AppList rendering 2`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests AppList rendering 3`] = `[Function]`;
|
||||||
|
|
||||||
|
exports[`app.tsx Tests AppList rendering 4`] = `[Function]`;
|
99
src/test/components/apps.spec.tsx
Normal file
99
src/test/components/apps.spec.tsx
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
AppCategory,
|
||||||
|
AppList,
|
||||||
|
IAppProps,
|
||||||
|
IAppCategoryProps,
|
||||||
|
IAppListProps,
|
||||||
|
} from "../../components/apps";
|
||||||
|
|
||||||
|
const appProps: IAppProps = {
|
||||||
|
name: "App Test",
|
||||||
|
icon: "bug_report",
|
||||||
|
url: "#",
|
||||||
|
displayURL: "test",
|
||||||
|
};
|
||||||
|
|
||||||
|
const appCategoryProps: Array<IAppCategoryProps> = [
|
||||||
|
{
|
||||||
|
name: "Test",
|
||||||
|
items: [appProps, appProps],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
items: [appProps, appProps],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const appListProps: Array<IAppListProps> = [
|
||||||
|
{
|
||||||
|
categories: appCategoryProps,
|
||||||
|
apps: [appProps, appProps],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apps: undefined,
|
||||||
|
categories: appCategoryProps,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apps: [appProps, appProps],
|
||||||
|
categories: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apps: undefined,
|
||||||
|
categories: undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("app.tsx", () => {
|
||||||
|
it("Tests App rendering with newTab", () => {
|
||||||
|
const tests = [true, false];
|
||||||
|
|
||||||
|
tests.forEach((test: boolean) => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<App
|
||||||
|
name={appProps.name}
|
||||||
|
icon={appProps.icon}
|
||||||
|
url={appProps.url}
|
||||||
|
displayURL={appProps.displayURL}
|
||||||
|
newTab={test}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests App rendering without newTab", () => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<App
|
||||||
|
name={appProps.name}
|
||||||
|
icon={appProps.icon}
|
||||||
|
url={appProps.url}
|
||||||
|
displayURL={appProps.displayURL}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests AppCategory rendering", () => {
|
||||||
|
appCategoryProps.forEach((appCategory) => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<AppCategory name={appCategory.name} items={appCategory.items} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests AppList rendering", () => {
|
||||||
|
appListProps.forEach((appList) => {
|
||||||
|
const { asFragment } = render(
|
||||||
|
<AppList apps={appList.apps} categories={appList.categories} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,7 +9,7 @@ import Settings, {
|
||||||
SectionHeadline,
|
SectionHeadline,
|
||||||
} from "../../components/settings";
|
} from "../../components/settings";
|
||||||
import { ISearchProps } from "../../components/searchBar";
|
import { ISearchProps } from "../../components/searchBar";
|
||||||
import { IThemeProps } from "../../lib/theme";
|
import { IThemeProps } from "../../lib/useTheme";
|
||||||
|
|
||||||
const themes: Array<IThemeProps> = [
|
const themes: Array<IThemeProps> = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getTheme, IThemeProps, setTheme } from "../../lib/theme";
|
import { getTheme, IThemeProps, setTheme } from "../../lib/useTheme";
|
||||||
|
|
||||||
const props: IThemeProps = {
|
const props: IThemeProps = {
|
||||||
label: "Classic",
|
label: "Classic",
|
||||||
|
|
Loading…
Reference in a new issue