From ca2f7a763d69c6717ec470c4c6ea176046777eb6 Mon Sep 17 00:00:00 2001 From: phntxx Date: Wed, 14 Jul 2021 01:18:11 +0200 Subject: [PATCH] Rename files, update tests --- src/app.tsx | 2 +- src/components/apps.tsx | 163 ++++++++++++++++++ src/components/settings.tsx | 2 +- src/lib/fetch.d.ts | 31 ++++ src/lib/fetch.tsx | 91 ++++++++++ src/lib/fetcher.tsx | 2 +- src/lib/{theme.tsx => useTheme.tsx} | 0 src/test/app.spec.tsx | 2 +- .../__snapshots__/apps.spec.tsx.snap | 19 ++ src/test/components/apps.spec.tsx | 99 +++++++++++ src/test/components/settings.spec.tsx | 2 +- src/test/lib/theme.spec.tsx | 2 +- 12 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 src/components/apps.tsx create mode 100644 src/lib/fetch.d.ts create mode 100644 src/lib/fetch.tsx rename src/lib/{theme.tsx => useTheme.tsx} (100%) create mode 100644 src/test/components/__snapshots__/apps.spec.tsx.snap create mode 100644 src/test/components/apps.spec.tsx diff --git a/src/app.tsx b/src/app.tsx index 4ed4a7e..2c5bfe1 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -7,7 +7,7 @@ import BookmarkList from "./components/bookmarks"; import Settings from "./components/settings"; 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 useMediaQuery from "./lib/useMediaQuery"; diff --git a/src/components/apps.tsx b/src/components/apps.tsx new file mode 100644 index 0000000..75fa40e --- /dev/null +++ b/src/components/apps.tsx @@ -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; +} + +export interface IAppListProps { + categories?: Array; + apps?: Array; +} + +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 ( + + + + + + {name} + {displayURL} + + + ); +}; + +/** + * 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) => ( + + {name && {name}} + + {items.map(({ name, icon, displayURL, newTab, url }, index) => ( + + + + ))} + + +); + +/** + * 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 ( + + Applications + {categories && + categories.map(({ name, items }, index) => ( + + ))} + {apps && ( + + )} + + ); + } else { + return <>; + } +}; + +export default AppList; diff --git a/src/components/settings.tsx b/src/components/settings.tsx index b40021f..8d655db 100644 --- a/src/components/settings.tsx +++ b/src/components/settings.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import Select from "./select"; import { ISearchProps } from "./searchBar"; -import { setTheme, IThemeProps, getTheme } from "../lib/theme"; +import { setTheme, IThemeProps, getTheme } from "../lib/useTheme"; import { Button, SubHeadline } from "./elements"; import Modal from "./modal"; diff --git a/src/lib/fetch.d.ts b/src/lib/fetch.d.ts new file mode 100644 index 0000000..1926e3a --- /dev/null +++ b/src/lib/fetch.d.ts @@ -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; +} diff --git a/src/lib/fetch.tsx b/src/lib/fetch.tsx new file mode 100644 index 0000000..b38994e --- /dev/null +++ b/src/lib/fetch.tsx @@ -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>; +} + +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(); + fetchList.app.setHook = setAppData; + + const [bookmarkData, setBookmarkData] = useState(); + fetchList.bookmarks.setHook = setBookmarkData; + + const [greeterData, setGreeterData] = useState(); + fetchList.greeter.setHook = setGreeterData; + + const [imprintData, setImprintData] = useState(); + fetchList.imprint.setHook = setImprintData; + + const [searchData, setSearchData] = useState(); + fetchList.search.setHook = setSearchData; + + const [themeData, setThemeData] = useState>(); + 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; diff --git a/src/lib/fetcher.tsx b/src/lib/fetcher.tsx index 076b741..5cb93bc 100644 --- a/src/lib/fetcher.tsx +++ b/src/lib/fetcher.tsx @@ -4,7 +4,7 @@ import { ISearchProps } from "../components/searchBar"; import { IBookmarkGroupProps } from "../components/bookmarks"; import { IAppCategoryProps } from "../components/appCategory"; import { IAppProps } from "../components/app"; -import { IThemeProps } from "./theme"; +import { IThemeProps } from "./useTheme"; import { IImprintProps } from "../components/imprint"; import { IGreeterProps } from "../components/greeter"; diff --git a/src/lib/theme.tsx b/src/lib/useTheme.tsx similarity index 100% rename from src/lib/theme.tsx rename to src/lib/useTheme.tsx diff --git a/src/test/app.spec.tsx b/src/test/app.spec.tsx index 0033368..50ac1e3 100644 --- a/src/test/app.spec.tsx +++ b/src/test/app.spec.tsx @@ -1,6 +1,6 @@ import { render } from "@testing-library/react"; import App, { GlobalStyle } from "../app"; -import { IThemeProps } from "../lib/theme"; +import { IThemeProps } from "../lib/useTheme"; const props: IThemeProps = { label: "Classic", diff --git a/src/test/components/__snapshots__/apps.spec.tsx.snap b/src/test/components/__snapshots__/apps.spec.tsx.snap new file mode 100644 index 0000000..bccc6d6 --- /dev/null +++ b/src/test/components/__snapshots__/apps.spec.tsx.snap @@ -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]`; diff --git a/src/test/components/apps.spec.tsx b/src/test/components/apps.spec.tsx new file mode 100644 index 0000000..235afa4 --- /dev/null +++ b/src/test/components/apps.spec.tsx @@ -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 = [ + { + name: "Test", + items: [appProps, appProps], + }, + { + name: "", + items: [appProps, appProps], + }, +]; + +const appListProps: Array = [ + { + 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( + , + ); + + expect(asFragment).toMatchSnapshot(); + }); + }); + + it("Tests App rendering without newTab", () => { + const { asFragment } = render( + , + ); + + expect(asFragment).toMatchSnapshot(); + }); + + it("Tests AppCategory rendering", () => { + appCategoryProps.forEach((appCategory) => { + const { asFragment } = render( + , + ); + + expect(asFragment).toMatchSnapshot(); + }); + }); + + it("Tests AppList rendering", () => { + appListProps.forEach((appList) => { + const { asFragment } = render( + , + ); + + expect(asFragment).toMatchSnapshot(); + }); + }); +}); diff --git a/src/test/components/settings.spec.tsx b/src/test/components/settings.spec.tsx index 4988cfd..57ea099 100644 --- a/src/test/components/settings.spec.tsx +++ b/src/test/components/settings.spec.tsx @@ -9,7 +9,7 @@ import Settings, { SectionHeadline, } from "../../components/settings"; import { ISearchProps } from "../../components/searchBar"; -import { IThemeProps } from "../../lib/theme"; +import { IThemeProps } from "../../lib/useTheme"; const themes: Array = [ { diff --git a/src/test/lib/theme.spec.tsx b/src/test/lib/theme.spec.tsx index 7b9ba8a..59ff6c5 100644 --- a/src/test/lib/theme.spec.tsx +++ b/src/test/lib/theme.spec.tsx @@ -1,4 +1,4 @@ -import { getTheme, IThemeProps, setTheme } from "../../lib/theme"; +import { getTheme, IThemeProps, setTheme } from "../../lib/useTheme"; const props: IThemeProps = { label: "Classic",