merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit b63efaaed60b46d911ec64df31485b913af0a7d1
parent 9ee8200672c49b91a6ee3ad3caf8c109d8c98d18
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon, 15 Mar 2021 10:42:58 -0300

refactor navigator, part 3

Diffstat:
MCHANGELOG.md | 6+++++-
Mpackages/frontend/src/AdminRoutes.tsx | 105+++----------------------------------------------------------------------------
Mpackages/frontend/src/ApplicationReadyRoutes.tsx | 24++++++++++--------------
Mpackages/frontend/src/InstanceRoutes.tsx | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mpackages/frontend/src/components/menu/SideBar.tsx | 48++++++++++++++++++++++--------------------------
Mpackages/frontend/src/components/menu/index.tsx | 3+--
Mpackages/frontend/src/index.tsx | 8++++----
Mpackages/frontend/src/routes/admin/list/index.tsx | 19+++++++++++++++----
Mpackages/frontend/src/routes/instance/orders/list/Table.tsx | 2+-
Mpackages/frontend/tests/header.test.tsx | 2+-
10 files changed, 139 insertions(+), 180 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -5,7 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Future work] + - change the admin title to "instances" if we are listing the instances and "settings: $ID" on updating instances (#6790) + - update title with: Taler Backoffice: $PAGE_TITLE (#6790) - notifications should tale place between title and content, and not disapear (#6788) + - if there is enough space for tables in mobile, make the scrollables (#6789) - complete product list information (#6792) - complete order list information (#6793) - gettext templates should be generated from the source code (#6791) @@ -33,8 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - create default instance if it is not already - - confirmation page when creating instances + - /o => /orders + /orders/$ORDER_ID main action => refund exchange errors diff --git a/packages/frontend/src/AdminRoutes.tsx b/packages/frontend/src/AdminRoutes.tsx @@ -15,51 +15,27 @@ */ import { h, VNode } from "preact"; import Router, { route, Route } from "preact-router"; -import { Redirect } from "./index"; -import { MerchantBackend } from "./declaration"; import { useMessageTemplate } from "preact-messages"; import { Notification } from "./utils/types"; -import { InstancePaths, InstanceRoutes } from "./InstanceRoutes"; import InstanceListPage from './routes/admin/list'; import InstanceCreatePage from "./routes/admin/create"; import NotFoundPage from './routes/notfound'; -import ProductListPage from './routes/instance/products/list' -import ProductCreatePage from './routes/instance/products/create' -import ProductUpdatePage from './routes/instance/products/update' - -import OrderListPage from './routes/instance/orders/list' -import OrderCreatePage from './routes/instance/orders/create' -import OrderUpdatePage from './routes/instance/orders/update' - -import TipListPage from './routes/instance/tips/list' -import TipCreatePage from './routes/instance/tips/create' -import TipUpdatePage from './routes/instance/tips/update' - -import TransferListPage from './routes/instance/transfers/list' -import TransferCreatePage from './routes/instance/transfers/create' -import LoginPage from "./routes/login"; -import { SwrError } from "./hooks/backend"; - export enum AdminPaths { - root = '/', list_instances = '/instances', - new_instance = '/new', + new_instance = '/instance/new', instance_id_route = '/instance/:id/:rest*', } interface Props { pushNotification: (n: Notification) => void; - instances: MerchantBackend.Instances.Instance[] + // instances: MerchantBackend.Instances.Instance[] } -export function AdminRoutes({ instances, pushNotification }: Props): VNode { +export function AdminRoutes({ pushNotification }: Props): VNode { const i18n = useMessageTemplate(); - const updateLoginStatus = () => null; - return <Router> - <Route path={AdminPaths.root} component={Redirect} to={AdminPaths.list_instances} /> <Route path={AdminPaths.list_instances} component={InstanceListPage} @@ -67,8 +43,6 @@ export function AdminRoutes({ instances, pushNotification }: Props): VNode { route(AdminPaths.new_instance); }} - instances={instances} - onUpdate={(id: string): void => { route(`/instance/${id}/update`); }} @@ -90,78 +64,7 @@ export function AdminRoutes({ instances, pushNotification }: Props): VNode { /> - <Route path={AdminPaths.instance_id_route} component={InstanceRoutes} - pushNotification={pushNotification} - parent="/instance/:id" - /> - - <Route path={InstancePaths.product_list} - component={ProductListPage} - onUnauthorized={() => <LoginPage - withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - - onLoadError={(error: SwrError) => <LoginPage - withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - /> - <Route path={InstancePaths.product_update} - component={ProductUpdatePage} - /> - <Route path={InstancePaths.product_new} - component={ProductCreatePage} - /> - - <Route path={InstancePaths.order_list} - component={OrderListPage} - onUnauthorized={() => <LoginPage - withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - - onLoadError={(error: SwrError) => <LoginPage - withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - /> - <Route path={InstancePaths.order_update} - component={OrderUpdatePage} - /> - <Route path={InstancePaths.order_new} - component={OrderCreatePage} - /> - - <Route path={InstancePaths.tips_list} - component={TipListPage} - onUnauthorized={() => <LoginPage - withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - - onLoadError={(error: SwrError) => <LoginPage - withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - /> - <Route path={InstancePaths.tips_update} - component={TipUpdatePage} - /> - <Route path={InstancePaths.tips_new} - component={TipCreatePage} - /> - - <Route path={InstancePaths.transfers_list} - component={TransferListPage} - onUnauthorized={() => <LoginPage - withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - - onLoadError={(error: SwrError) => <LoginPage - withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - /> - <Route path={InstancePaths.transfers_new} - component={TransferCreatePage} - /> - - - <Route default component={NotFoundPage} /> + {/* <Route default component={undefined} /> */} </Router> } \ No newline at end of file diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx b/packages/frontend/src/ApplicationReadyRoutes.tsx @@ -26,7 +26,7 @@ import { useBackendInstances } from "./hooks/backend"; import { InstanceRoutes } from "./InstanceRoutes"; import LoginPage from './routes/login'; import { INSTANCE_ID_LOOKUP } from './utils/constants'; -import { Menu } from './components/menu'; +import { NotYetReadyAppMenu, Menu } from './components/menu'; import { AdminRoutes } from './AdminRoutes'; import { useMessageTemplate } from 'preact-messages'; interface Props { @@ -45,7 +45,7 @@ export function ApplicationReadyRoutes({ pushNotification }: Props): VNode { if (!list.data) { if (list.unauthorized) { return <Fragment> - <Menu title="Login" onLogout={() => { + <NotYetReadyAppMenu title="Login" onLogout={() => { clearAllTokens(); route('/') }} /> @@ -63,7 +63,7 @@ export function ApplicationReadyRoutes({ pushNotification }: Props): VNode { // query to /config is ok but the URL // doest not match with our pattern return <Fragment> - <Menu title="Error" onLogout={() => { + <NotYetReadyAppMenu title="Error" onLogout={() => { clearAllTokens(); route('/') }} /> @@ -73,21 +73,19 @@ export function ApplicationReadyRoutes({ pushNotification }: Props): VNode { /> </Fragment> } + return <Fragment> <Menu instance={match[1]} onLogout={() => { clearAllTokens(); route('/') }} /> - <InstanceRoutes - id={match[1]} - pushNotification={pushNotification} - /> + <InstanceRoutes id={match[1]} pushNotification={pushNotification} /> </Fragment> } - + if (list.error) { return <Fragment> - <Menu title="Error" /> + <NotYetReadyAppMenu title="Error" /> <LoginPage withMessage={{ message: i18n`Couldnt access the server`, description: list.error.message, type: 'ERROR', }} onConfirm={updateLoginStatus} @@ -96,16 +94,14 @@ export function ApplicationReadyRoutes({ pushNotification }: Props): VNode { } // is loading - return <Menu /> + return <NotYetReadyAppMenu title="Loading..." /> } return <Fragment> - <Menu onLogout={() => { + <Menu instance="default" admin onLogout={() => { clearAllTokens(); route('/') }} /> - <AdminRoutes instances={list.data.instances} - pushNotification={pushNotification} - /> + <InstanceRoutes admin id="default" pushNotification={pushNotification} /> </Fragment> } diff --git a/packages/frontend/src/InstanceRoutes.tsx b/packages/frontend/src/InstanceRoutes.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode } from 'preact'; +import { Fragment, h, VNode } from 'preact'; import { useCallback, useEffect, useMemo } from "preact/hooks"; import { Route, Router, route } from 'preact-router'; import { useMessageTemplate } from 'preact-messages'; @@ -47,34 +47,45 @@ import TipUpdatePage from './routes/instance/tips/update' import TransferListPage from './routes/instance/transfers/list' import TransferCreatePage from './routes/instance/transfers/create' +// import { AdminRoutes } from './AdminRoutes'; +import InstanceListPage from './routes/admin/list'; +import InstanceCreatePage from "./routes/admin/create"; export enum InstancePaths { details = '/', update = '/update', - product_list = '/p', - product_update = '/p/:pid/update', - product_new = '/p/new', + product_list = '/products', + product_update = '/product/:pid/update', + product_new = '/product/new', - order_list = '/o', + order_list = '/orders', order_update = '/p/:oid/update', order_new = '/o/new', - tips_list = '/r', - tips_update = '/r/:rid/update', - tips_new = '/r/new', + tips_list = '/tips', + tips_update = '/tip/:rid/update', + tips_new = '/tip/new', - transfers_list = '/t', - transfers_new = '/t/new', + transfers_list = '/transfers', + transfers_new = '/transfer/new', +} + +export enum AdminPaths { + list_instances = '/instances', + new_instance = '/instance/new', + instance_id_route = '/instance/:id/:rest*', } export interface Props { id: string; pushNotification: (n: Notification) => void; - parent?: string; + admin?: boolean; } -export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { +const rootPath = typeof window !== 'undefined' ? window.location.pathname : '/' + +export function InstanceRoutes({ id, pushNotification, admin }: Props): VNode { const [token, updateToken] = useBackendInstanceToken(id); const { changeBackend, addTokenCleaner } = useBackendContext(); const cleaner = useCallback(() => { updateToken(undefined); }, [id]); @@ -90,11 +101,50 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { updateToken(token); }; - const value = useMemo(() => ({ id, token, admin: !!parent }), [id, token]) + const value = useMemo(() => ({ id, token, admin }), [id, token]) return <InstanceContextProvider value={value}> <Router> - <Route path={(!parent ? "" : parent) + InstancePaths.details} + {admin && + <Route path={rootPath + AdminPaths.list_instances} component={InstanceListPage} + + onCreate={() => { + route(AdminPaths.new_instance); + }} + + onUpdate={(id: string): void => { + route(`/instance/${id}/update`); + }} + + onUnauthorized={() => <LoginPage + withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} + onConfirm={updateLoginStatus} />} + + onLoadError={(error: SwrError) => <LoginPage + withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} + onConfirm={updateLoginStatus} />} + + /> + } + + {admin && + <Route path={rootPath + AdminPaths.new_instance} component={InstanceCreatePage} + + onBack={() => route(AdminPaths.list_instances)} + + onConfirm={() => { + pushNotification({ message: i18n`create_success`, type: 'SUCCESS' }); + route(AdminPaths.list_instances); + }} + + onError={(error: any) => { + pushNotification({ message: i18n`create_error`, type: 'ERROR' }); + }} + + /> + } + + <Route path={rootPath + InstancePaths.details} component={DetailPage} onUnauthorized={() => <LoginPage @@ -106,7 +156,7 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { onConfirm={updateLoginStatus} />} /> - <Route path={(!parent ? "" : parent) + InstancePaths.update} + <Route path={rootPath + InstancePaths.update} component={InstanceUpdatePage} onUnauthorized={() => <LoginPage @@ -131,7 +181,7 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { }} /> - <Route path={(!parent ? "" : parent) + InstancePaths.product_list} + <Route path={rootPath + InstancePaths.product_list} component={ProductListPage} onUnauthorized={() => <LoginPage withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} @@ -141,14 +191,14 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} onConfirm={updateLoginStatus} />} /> - <Route path={(!parent ? "" : parent) + InstancePaths.product_update} + <Route path={rootPath + InstancePaths.product_update} component={ProductUpdatePage} /> - <Route path={(!parent ? "" : parent) + InstancePaths.product_new} + <Route path={rootPath + InstancePaths.product_new} component={ProductCreatePage} /> - <Route path={(!parent ? "" : parent) + InstancePaths.order_list} + <Route path={rootPath + InstancePaths.order_list} component={OrderListPage} onUnauthorized={() => <LoginPage withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} @@ -158,14 +208,14 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} onConfirm={updateLoginStatus} />} /> - <Route path={(!parent ? "" : parent) + InstancePaths.order_update} + <Route path={rootPath + InstancePaths.order_update} component={OrderUpdatePage} /> - <Route path={(!parent ? "" : parent) + InstancePaths.order_new} + <Route path={rootPath + InstancePaths.order_new} component={OrderCreatePage} /> - <Route path={(!parent ? "" : parent) + InstancePaths.tips_list} + <Route path={rootPath + InstancePaths.tips_list} component={TipListPage} onUnauthorized={() => <LoginPage withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} @@ -175,14 +225,14 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} onConfirm={updateLoginStatus} />} /> - <Route path={(!parent ? "" : parent) + InstancePaths.tips_update} + <Route path={rootPath + InstancePaths.tips_update} component={TipUpdatePage} /> - <Route path={(!parent ? "" : parent) + InstancePaths.tips_new} + <Route path={rootPath + InstancePaths.tips_new} component={TipCreatePage} /> - <Route path={(!parent ? "" : parent) + InstancePaths.transfers_list} + <Route path={rootPath + InstancePaths.transfers_list} component={TransferListPage} onUnauthorized={() => <LoginPage withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} @@ -192,7 +242,7 @@ export function InstanceRoutes({ id, pushNotification, parent }: Props): VNode { withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} onConfirm={updateLoginStatus} />} /> - <Route path={(!parent ? "" : parent) + InstancePaths.transfers_new} + <Route path={rootPath + InstancePaths.transfers_new} component={TransferCreatePage} /> diff --git a/packages/frontend/src/components/menu/SideBar.tsx b/packages/frontend/src/components/menu/SideBar.tsx @@ -28,10 +28,11 @@ import { LangSelector } from './LangSelector'; interface Props { onLogout: () => void; mobile?: boolean; - instance?: string; + instance: string; + admin?: boolean; } -export function Sidebar({ mobile, instance, onLogout }: Props): VNode { +export function Sidebar({ mobile, instance, onLogout, admin }: Props): VNode { const config = useConfigContext(); const backend = useBackendContext(); @@ -47,47 +48,34 @@ export function Sidebar({ mobile, instance, onLogout }: Props): VNode { </div> </div> <div class="menu is-menu-main"> - {!instance && <Fragment> - <p class="menu-label">General</p> - <ul class="menu-list"> - <li> - <a href="/" class="is-active router-link-active has-icon"> - <span class="icon"><i class="mdi mdi-desktop-mac" /></span> - <span class="menu-item-label">Instances</span> - </a> - </li> - </ul> - </Fragment>} <p class="menu-label">Instance</p> <ul class="menu-list"> - {instance && <Fragment> - <li> - <a href="/update" class="has-icon"> - <span class="icon"><i class="mdi mdi-square-edit-outline" /></span> - <span class="menu-item-label">Settings</span> - </a> - </li> - </Fragment>} <li> - <a href="/o" class="has-icon"> + <a href="/update" class="has-icon"> + <span class="icon"><i class="mdi mdi-square-edit-outline" /></span> + <span class="menu-item-label">Settings</span> + </a> + </li> + <li> + <a href="/orders" class="has-icon"> <span class="icon"><i class="mdi mdi-cash-register" /></span> <span class="menu-item-label">Orders</span> </a> </li> <li> - <a href="/p" class="has-icon"> - <span class="icon"><i class="mdi mdi-shopping" /></span> + <a href="/products" class="has-icon"> + <span class="icon"><i class="mdi mdi-mdi-shopping" /></span> <span class="menu-item-label">Products</span> </a> </li> <li> - <a href="/t" class="has-icon"> + <a href="/transfers" class="has-icon"> <span class="icon"><i class="mdi mdi-bank" /></span> <span class="menu-item-label">Transfers</span> </a> </li> <li> - <a href="/r" class="has-icon"> + <a href="/tips" class="has-icon"> <span class="icon"><i class="mdi mdi-cash" /></span> <span class="menu-item-label">Tips</span> </a> @@ -117,6 +105,14 @@ export function Sidebar({ mobile, instance, onLogout }: Props): VNode { </span> </div> </li> + {admin && + <li> + <a href="/instance/new" class="has-icon"> + <span class="icon"><i class="mdi mdi-plus" /></span> + <span class="menu-item-label">New Instance</span> + </a> + </li> + } <li> <a class="has-icon is-state-info is-hoverable" onClick={(): void => onLogout()}> <span class="icon"><i class="mdi mdi-logout default" /></span> diff --git a/packages/frontend/src/components/menu/index.tsx b/packages/frontend/src/components/menu/index.tsx @@ -98,4 +98,4 @@ </div> } - -\ No newline at end of file + diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx @@ -36,7 +36,7 @@ import { hasKey, onTranslationError } from "./utils/functions"; import LoginPage from './routes/login'; import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes"; -import { Menu } from "./components/menu"; +import { NotYetReadyAppMenu } from "./components/menu"; export function Redirect({ to }: { to: string }): null { useEffect(() => { @@ -68,7 +68,7 @@ function ApplicationStatusRoutes(): VNode { if (!triedToLog) { return <div id="app"> - <Menu title="Welcome!" /> + <NotYetReadyAppMenu title="Welcome!" /> <LoginPage onConfirm={(url: string, token?: string) => { changeBackend(url) @@ -85,7 +85,7 @@ function ApplicationStatusRoutes(): VNode { if (backendConfig.unauthorized) { return <div id="app"> - <Menu title="Login" /> + <NotYetReadyAppMenu title="Login" /> <LoginPage onConfirm={(url: string, token?: string) => { changeBackend(url) @@ -97,7 +97,7 @@ function ApplicationStatusRoutes(): VNode { } return <div id="app"> - <Menu title="Error" /> + <NotYetReadyAppMenu title="Error" /> <LoginPage withMessage={{ message: i18n`Couldnt access the server`, diff --git a/packages/frontend/src/routes/admin/list/index.tsx b/packages/frontend/src/routes/admin/list/index.tsx @@ -21,7 +21,7 @@ import { Fragment, h, VNode } from 'preact'; import { View } from './View'; -import { useAdminMutateAPI } from '../../../hooks/backend'; +import { SwrError, useAdminMutateAPI, useBackendInstances } from '../../../hooks/backend'; import { useState } from 'preact/hooks'; import { MerchantBackend } from '../../../declaration'; import { Notification } from '../../../utils/types'; @@ -31,15 +31,26 @@ interface Props { pushNotification: (n: Notification) => void; onCreate: () => void; onUpdate: (id: string) => void; - instances: MerchantBackend.Instances.Instance[] + instances: MerchantBackend.Instances.Instance[]; + onUnauthorized: () => VNode; + onLoadError: (e: SwrError) => VNode; } -export default function Instances({ pushNotification, instances, onCreate, onUpdate }: Props): VNode { +export default function Instances({ pushNotification, onUnauthorized, onLoadError, onCreate, onUpdate }: Props): VNode { + const result = useBackendInstances() const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance | null>(null) const { deleteInstance } = useAdminMutateAPI() + if (!result.data) { + if (result.unauthorized) return onUnauthorized() + if (result.error) return onLoadError(result.error) + return <div> + loading .... + </div> + } + return <Fragment> - <View instances={instances} + <View instances={result.data.instances} isLoading={false} onDelete={setDeleting} onCreate={onCreate} diff --git a/packages/frontend/src/routes/instance/orders/list/Table.tsx b/packages/frontend/src/routes/instance/orders/list/Table.tsx @@ -26,7 +26,7 @@ import { MerchantBackend, WidthId } from "../../../../declaration" import { Actions, buildActions } from "../../../../utils/table"; type Entity = MerchantBackend.Orders.OrderHistoryEntry & { id: string } - interface Props { +interface Props { instances: Entity[]; onUpdate: (id: string) => void; onDelete: (id: Entity) => void; diff --git a/packages/frontend/tests/header.test.tsx b/packages/frontend/tests/header.test.tsx @@ -31,7 +31,7 @@ describe('Initial Test of the Sidebar', () => { }) test('Sidbar renders anchors with text', () => { - const context = shallow(<Sidebar onLogout={() => {}} />); + const context = shallow(<Sidebar instance="default" onLogout={() => {}} />); expect(context.find('a').map( a => a.text())).toEqual(["Instances", "Details", "Orders", "Inventory", "Tipping", "Log out"]); });