Rename files, update tests

This commit is contained in:
phntxx 2021-07-14 01:18:11 +02:00
parent 786aca82f1
commit ca2f7a763d
12 changed files with 409 additions and 6 deletions

View file

@ -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
View 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;

View file

@ -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
View 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
View 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;

View file

@ -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";

View file

@ -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",

View 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]`;

View 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();
});
});
});

View file

@ -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> = [
{ {

View file

@ -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",