2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

[PUI] Quick commands pallet (#6987)

* add spotlight

* [PUI] Quick commands pallet
Fixes #5888

* add testing for new commands

* add text input testing

* only test backend if code changed

* add trans files

* fix testing text

* always push coverage

* add nav state to manage navigation state

* add navigation action and test

* make test faster

* fix typo

* use texts instead

* fix tests for linux

* use var to determine action key

* Revert "use texts instead"

This reverts commit 77711895569a42426a422d679496661c0ee9d25b.

* add wait for input

* split out keyboard based tests

* split ou test

* add upload

* revert assert change

* adjust reporting settings

* ignore error code

* fix reporter config

* add full info suit (+tests)

* make tests more accurate

* license modal fixes

* unify icons

* add custom actions registering
with removal on page refresh

* only upload report data if the tests failed

* Revert "add trans files"

This reverts commit 28d96e058febba96979afd3054e04cd207bffba5.

* adjust url that iw waited for

* try an await and body locator for keypresses

* test registering addition actions

* extend testing for actions

* add doclink and test

* merge tests
This commit is contained in:
Matthias Mair 2024-04-11 23:20:00 +01:00 committed by GitHub
parent ff8eeca8c0
commit cbbdb70762
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 377 additions and 32 deletions

View File

@ -27,6 +27,7 @@
"@mantine/hooks": "<7", "@mantine/hooks": "<7",
"@mantine/modals": "<7", "@mantine/modals": "<7",
"@mantine/notifications": "<7", "@mantine/notifications": "<7",
"@mantine/spotlight": "<7",
"@naisutech/react-tree": "^3.1.0", "@naisutech/react-tree": "^3.1.0",
"@sentry/react": "^7.109.0", "@sentry/react": "^7.109.0",
"@tabler/icons-react": "^3.1.0", "@tabler/icons-react": "^3.1.0",

View File

@ -0,0 +1,15 @@
import { t } from '@lingui/macro';
import { ActionIcon } from '@mantine/core';
import { spotlight } from '@mantine/spotlight';
import { IconCommand } from '@tabler/icons-react';
/**
* A button which opens the quick command modal
*/
export function SpotlightButton() {
return (
<ActionIcon onClick={() => spotlight.open()} title={t`Open spotlight`}>
<IconCommand />
</ActionIcon>
);
}

View File

@ -16,6 +16,10 @@ export interface MenuLinkItem {
docchildren?: React.ReactNode; docchildren?: React.ReactNode;
} }
export type menuItemsCollection = {
[key: string]: MenuLinkItem;
};
function ConditionalDocTooltip({ function ConditionalDocTooltip({
item, item,
children children

View File

@ -2,7 +2,7 @@ import { ActionIcon, Container, Group, Indicator, Tabs } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { IconBell, IconSearch } from '@tabler/icons-react'; import { IconBell, IconSearch } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { useMatch, useNavigate, useParams } from 'react-router-dom'; import { useMatch, useNavigate, useParams } from 'react-router-dom';
import { api } from '../../App'; import { api } from '../../App';
@ -10,7 +10,9 @@ import { navTabs as mainNavTabs } from '../../defaults/links';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { InvenTreeStyle } from '../../globalStyle'; import { InvenTreeStyle } from '../../globalStyle';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { ScanButton } from '../buttons/ScanButton'; import { ScanButton } from '../buttons/ScanButton';
import { SpotlightButton } from '../buttons/SpotlightButton';
import { MainMenu } from './MainMenu'; import { MainMenu } from './MainMenu';
import { NavHoverMenu } from './NavHoverMenu'; import { NavHoverMenu } from './NavHoverMenu';
import { NavigationDrawer } from './NavigationDrawer'; import { NavigationDrawer } from './NavigationDrawer';
@ -19,8 +21,12 @@ import { SearchDrawer } from './SearchDrawer';
export function Header() { export function Header() {
const { classes } = InvenTreeStyle(); const { classes } = InvenTreeStyle();
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
state.setNavigationOpen,
state.navigationOpen
]);
const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] = const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] =
useDisclosure(false); useDisclosure(navigationOpen);
const [ const [
searchDrawerOpened, searchDrawerOpened,
{ open: openSearchDrawer, close: closeSearchDrawer } { open: openSearchDrawer, close: closeSearchDrawer }
@ -59,6 +65,18 @@ export function Header() {
refetchOnWindowFocus: false refetchOnWindowFocus: false
}); });
// Sync Navigation Drawer state with zustand
useEffect(() => {
if (navigationOpen === navDrawerOpened) return;
setNavigationOpen(navDrawerOpened);
}, [navDrawerOpened]);
useEffect(() => {
if (navigationOpen === navDrawerOpened) return;
if (navigationOpen) openNavDrawer();
else closeNavDrawer();
}, [navigationOpen]);
return ( return (
<div className={classes.layoutHeader}> <div className={classes.layoutHeader}>
<SearchDrawer opened={searchDrawerOpened} onClose={closeSearchDrawer} /> <SearchDrawer opened={searchDrawerOpened} onClose={closeSearchDrawer} />
@ -80,6 +98,7 @@ export function Header() {
<ActionIcon onClick={openSearchDrawer}> <ActionIcon onClick={openSearchDrawer}>
<IconSearch /> <IconSearch />
</ActionIcon> </ActionIcon>
<SpotlightButton />
<ScanButton /> <ScanButton />
<ActionIcon onClick={openNotificationDrawer}> <ActionIcon onClick={openNotificationDrawer}>
<Indicator <Indicator

View File

@ -1,6 +1,11 @@
import { t } from '@lingui/macro';
import { Container, Flex, Space } from '@mantine/core'; import { Container, Flex, Space } from '@mantine/core';
import { Navigate, Outlet, useLocation } from 'react-router-dom'; import { SpotlightProvider } from '@mantine/spotlight';
import { IconSearch } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { getActions } from '../../defaults/actions';
import { InvenTreeStyle } from '../../globalStyle'; import { InvenTreeStyle } from '../../globalStyle';
import { useSessionState } from '../../states/SessionState'; import { useSessionState } from '../../states/SessionState';
import { Footer } from './Footer'; import { Footer } from './Footer';
@ -22,9 +27,34 @@ export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
export default function LayoutComponent() { export default function LayoutComponent() {
const { classes } = InvenTreeStyle(); const { classes } = InvenTreeStyle();
const navigate = useNavigate();
const location = useLocation();
const defaultactions = getActions(navigate);
const [actions, setActions] = useState(defaultactions);
const [customActions, setCustomActions] = useState<boolean>(false);
function actionsAreChanging(change: []) {
if (change.length > defaultactions.length) setCustomActions(true);
setActions(change);
}
useEffect(() => {
if (customActions) {
setActions(defaultactions);
setCustomActions(false);
}
}, [location]);
return ( return (
<ProtectedRoute> <ProtectedRoute>
<SpotlightProvider
actions={actions}
onActionsChange={actionsAreChanging}
searchIcon={<IconSearch size="1.2rem" />}
searchPlaceholder={t`Search...`}
shortcut={['mod + K', '/']}
nothingFoundMessage={t`Nothing found...`}
>
<Flex direction="column" mih="100vh"> <Flex direction="column" mih="100vh">
<Header /> <Header />
<Container className={classes.layoutContent} size="100%"> <Container className={classes.layoutContent} size="100%">
@ -33,6 +63,7 @@ export default function LayoutComponent() {
<Space h="xl" /> <Space h="xl" />
<Footer /> <Footer />
</Flex> </Flex>
</SpotlightProvider>
</ProtectedRoute> </ProtectedRoute>
); );
} }

View File

@ -20,6 +20,8 @@ import { useLocalState } from '../../states/LocalState';
import { InvenTreeLogo } from '../items/InvenTreeLogo'; import { InvenTreeLogo } from '../items/InvenTreeLogo';
import { MenuLinks } from '../items/MenuLinks'; import { MenuLinks } from '../items/MenuLinks';
const onlyItems = Object.values(menuItems);
export function NavHoverMenu({ export function NavHoverMenu({
openDrawer: openDrawer openDrawer: openDrawer
}: { }: {
@ -85,7 +87,7 @@ export function NavHoverMenu({
mx="-md" mx="-md"
color={theme.colorScheme === 'dark' ? 'dark.5' : 'gray.1'} color={theme.colorScheme === 'dark' ? 'dark.5' : 'gray.1'}
/> />
<MenuLinks links={menuItems} highlighted={true} /> <MenuLinks links={onlyItems} highlighted={true} />
<div className={classes.headerDropdownFooter}> <div className={classes.headerDropdownFooter}>
<Group position="apart"> <Group position="apart">
<div> <div>

View File

@ -18,6 +18,7 @@ import { MenuLinkItem, MenuLinks } from '../items/MenuLinks';
// TODO @matmair #1: implement plugin loading and menu item generation see #5269 // TODO @matmair #1: implement plugin loading and menu item generation see #5269
const plugins: MenuLinkItem[] = []; const plugins: MenuLinkItem[] = [];
const onlyItems = Object.values(menuItems);
export function NavigationDrawer({ export function NavigationDrawer({
opened, opened,
@ -60,7 +61,7 @@ function DrawerContent() {
<Container className={classes.layoutContent} p={0}> <Container className={classes.layoutContent} p={0}>
<ScrollArea h={scrollHeight} type="always" offsetScrollbars> <ScrollArea h={scrollHeight} type="always" offsetScrollbars>
<Title order={5}>{t`Pages`}</Title> <Title order={5}>{t`Pages`}</Title>
<MenuLinks links={menuItems} /> <MenuLinks links={onlyItems} />
<Space h="md" /> <Space h="md" />
{plugins.length > 0 ? ( {plugins.length > 0 ? (
<> <>

View File

@ -101,6 +101,7 @@ export function LanguageContext({ children }: { children: JSX.Element }) {
// Clear out cached table column names // Clear out cached table column names
useLocalState.getState().clearTableColumnNames(); useLocalState.getState().clearTableColumnNames();
}) })
/* istanbul ignore next */
.catch((err) => { .catch((err) => {
console.error('Failed loading translations', err); console.error('Failed loading translations', err);
if (isMounted.current) setLoadedState('error'); if (isMounted.current) setLoadedState('error');
@ -115,6 +116,7 @@ export function LanguageContext({ children }: { children: JSX.Element }) {
return <LoadingOverlay visible={true} />; return <LoadingOverlay visible={true} />;
} }
/* istanbul ignore next */
if (loadedState === 'error') { if (loadedState === 'error') {
return ( return (
<Text> <Text>

View File

@ -0,0 +1,59 @@
import { t } from '@lingui/macro';
import type { SpotlightAction } from '@mantine/spotlight';
import { IconHome, IconLink, IconPointer } from '@tabler/icons-react';
import { NavigateFunction } from 'react-router-dom';
import { useLocalState } from '../states/LocalState';
import { aboutInvenTree, docLinks, licenseInfo, serverInfo } from './links';
import { menuItems } from './menuItems';
export function getActions(navigate: NavigateFunction) {
const setNavigationOpen = useLocalState((state) => state.setNavigationOpen);
const actions: SpotlightAction[] = [
{
title: t`Home`,
description: `Go to the home page`,
onTrigger: () => navigate(menuItems.home.link),
icon: <IconHome size="1.2rem" />
},
{
title: t`Dashboard`,
description: t`Go to the InvenTree dashboard`,
onTrigger: () => navigate(menuItems.dashboard.link),
icon: <IconLink size="1.2rem" />
},
{
title: t`Documentation`,
description: t`Visit the documentation to learn more about InvenTree`,
onTrigger: () => (window.location.href = docLinks.faq),
icon: <IconLink size="1.2rem" />
},
{
title: t`About InvenTree`,
description: t`About the InvenTree org`,
onTrigger: () => aboutInvenTree(),
icon: <IconLink size="1.2rem" />
},
{
title: t`Server Information`,
description: t`About this Inventree instance`,
onTrigger: () => serverInfo(),
icon: <IconLink size="1.2rem" />
},
{
title: t`License Information`,
description: t`Licenses for dependencies of the service`,
onTrigger: () => licenseInfo(),
icon: <IconLink size="1.2rem" />
},
{
title: t`Open Navigation`,
description: t`Open the main navigation menu`,
onTrigger: () => setNavigationOpen(true),
icon: <IconPointer size="1.2rem" />
}
];
return actions;
}

View File

@ -71,7 +71,7 @@ export const navDocLinks: DocumentationLinkItem[] = [
} }
]; ];
function serverInfo() { export function serverInfo() {
return openContextModal({ return openContextModal({
modal: 'info', modal: 'info',
title: ( title: (
@ -84,7 +84,7 @@ function serverInfo() {
}); });
} }
function aboutInvenTree() { export function aboutInvenTree() {
return openContextModal({ return openContextModal({
modal: 'about', modal: 'about',
title: ( title: (
@ -96,7 +96,8 @@ function aboutInvenTree() {
innerProps: {} innerProps: {}
}); });
} }
function licenseInfo() {
export function licenseInfo() {
return openContextModal({ return openContextModal({
modal: 'license', modal: 'license',
title: ( title: (

View File

@ -1,75 +1,75 @@
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
import { MenuLinkItem } from '../components/items/MenuLinks'; import { menuItemsCollection } from '../components/items/MenuLinks';
import { IS_DEV_OR_DEMO } from '../main'; import { IS_DEV_OR_DEMO } from '../main';
export const menuItems: MenuLinkItem[] = [ export const menuItems: menuItemsCollection = {
{ home: {
id: 'home', id: 'home',
text: <Trans>Home</Trans>, text: <Trans>Home</Trans>,
link: '/', link: '/',
highlight: true highlight: true
}, },
{ profile: {
id: 'profile', id: 'profile',
text: <Trans>Account settings</Trans>, text: <Trans>Account settings</Trans>,
link: '/settings/user', link: '/settings/user',
doctext: <Trans>User attributes and design settings.</Trans> doctext: <Trans>User attributes and design settings.</Trans>
}, },
{ scan: {
id: 'scan', id: 'scan',
text: <Trans>Scanning</Trans>, text: <Trans>Scanning</Trans>,
link: '/scan', link: '/scan',
doctext: <Trans>View for interactive scanning and multiple actions.</Trans>, doctext: <Trans>View for interactive scanning and multiple actions.</Trans>,
highlight: true highlight: true
}, },
{ dashboard: {
id: 'dashboard', id: 'dashboard',
text: <Trans>Dashboard</Trans>, text: <Trans>Dashboard</Trans>,
link: '/dashboard' link: '/dashboard'
}, },
{ parts: {
id: 'parts', id: 'parts',
text: <Trans>Parts</Trans>, text: <Trans>Parts</Trans>,
link: '/part/' link: '/part/'
}, },
{ stock: {
id: 'stock', id: 'stock',
text: <Trans>Stock</Trans>, text: <Trans>Stock</Trans>,
link: '/stock' link: '/stock'
}, },
{ build: {
id: 'build', id: 'build',
text: <Trans>Build</Trans>, text: <Trans>Build</Trans>,
link: '/build/' link: '/build/'
}, },
{ purchasing: {
id: 'purchasing', id: 'purchasing',
text: <Trans>Purchasing</Trans>, text: <Trans>Purchasing</Trans>,
link: '/purchasing/' link: '/purchasing/'
}, },
{ sales: {
id: 'sales', id: 'sales',
text: <Trans>Sales</Trans>, text: <Trans>Sales</Trans>,
link: '/sales/' link: '/sales/'
}, },
{ 'settings-system': {
id: 'settings-system', id: 'settings-system',
text: <Trans>System Settings</Trans>, text: <Trans>System Settings</Trans>,
link: '/settings/system' link: '/settings/system'
}, },
{ 'settings-admin': {
id: 'settings-admin', id: 'settings-admin',
text: <Trans>Admin Center</Trans>, text: <Trans>Admin Center</Trans>,
link: '/settings/admin' link: '/settings/admin'
} }
]; };
if (IS_DEV_OR_DEMO) { if (IS_DEV_OR_DEMO) {
menuItems.push({ menuItems['playground'] = {
id: 'playground', id: 'playground',
text: <Trans>Playground</Trans>, text: <Trans>Playground</Trans>,
link: '/playground', link: '/playground',
highlight: true highlight: true
}); };
} }

View File

@ -2,6 +2,8 @@ import { Trans } from '@lingui/macro';
import { Button, Card, Stack, TextInput } from '@mantine/core'; import { Button, Card, Stack, TextInput } from '@mantine/core';
import { Group, Text } from '@mantine/core'; import { Group, Text } from '@mantine/core';
import { Accordion } from '@mantine/core'; import { Accordion } from '@mantine/core';
import { spotlight } from '@mantine/spotlight';
import { IconAlien } from '@tabler/icons-react';
import { ReactNode, useMemo, useState } from 'react'; import { ReactNode, useMemo, useState } from 'react';
import { OptionsApiForm } from '../../components/forms/ApiForm'; import { OptionsApiForm } from '../../components/forms/ApiForm';
@ -167,6 +169,38 @@ function StatusLabelPlayground() {
); );
} }
// Sample for spotlight actions
function SpotlighPlayground() {
return (
<Button
variant="outline"
onClick={() => {
spotlight.registerActions([
{
id: 'secret-action-1',
title: 'Secret action',
description: 'It was registered with a button click',
icon: <IconAlien size="1.2rem" />,
onTrigger: () => console.log('Secret')
},
{
id: 'secret-action-2',
title: 'Another secret action',
description:
'You can register multiple actions with just one command',
icon: <IconAlien size="1.2rem" />,
onTrigger: () => console.log('Secret')
}
]);
console.log('registed');
spotlight.open();
}}
>
Register extra actions
</Button>
);
}
/** Construct a simple accordion group with title and content */ /** Construct a simple accordion group with title and content */
function PlaygroundArea({ function PlaygroundArea({
title, title,
@ -207,6 +241,10 @@ export default function Playground() {
title="Status labels" title="Status labels"
content={<StatusLabelPlayground />} content={<StatusLabelPlayground />}
/> />
<PlaygroundArea
title="Spotlight actions"
content={<SpotlighPlayground />}
/>
</Accordion> </Accordion>
</> </>
); );

View File

@ -31,6 +31,8 @@ interface LocalStateProps {
clearTableColumnNames: () => void; clearTableColumnNames: () => void;
detailDrawerStack: number; detailDrawerStack: number;
addDetailDrawer: (value: number | false) => void; addDetailDrawer: (value: number | false) => void;
navigationOpen: boolean;
setNavigationOpen: (value: boolean) => void;
} }
export const useLocalState = create<LocalStateProps>()( export const useLocalState = create<LocalStateProps>()(
@ -87,6 +89,11 @@ export const useLocalState = create<LocalStateProps>()(
detailDrawerStack: detailDrawerStack:
value === false ? 0 : get().detailDrawerStack + value value === false ? 0 : get().detailDrawerStack + value
}); });
},
// navigation
navigationOpen: false,
setNavigationOpen: (value) => {
set({ navigationOpen: value });
} }
}), }),
{ {

View File

@ -1,11 +1,22 @@
import { test as baseTest } from '@playwright/test'; import { test as baseTest } from '@playwright/test';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as fs from 'fs'; import * as fs from 'fs';
import os from 'os';
import * as path from 'path'; import * as path from 'path';
const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output'); const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output');
export const classicUrl = 'http://127.0.0.1:8000'; export const classicUrl = 'http://127.0.0.1:8000';
let platform = os.platform();
let systemKeyVar;
if (platform === 'darwin') {
systemKeyVar = 'Meta';
} else {
systemKeyVar = 'Control';
}
/* metaKey is the local action key (used for spotlight for example) */
export const systemKey = systemKeyVar;
export function generateUUID(): string { export function generateUUID(): string {
return crypto.randomBytes(16).toString('hex'); return crypto.randomBytes(16).toString('hex');
} }

View File

@ -0,0 +1,147 @@
import { expect, systemKey, test } from './baseFixtures.js';
test('PUI - Quick Command', async ({ page }) => {
await page.goto('./platform/');
await expect(page).toHaveTitle('InvenTree');
await page.waitForURL('**/platform/');
await page.getByLabel('username').fill('allaccess');
await page.getByLabel('password').fill('nolimits');
await page.getByRole('button', { name: 'Log in' }).click();
await page.waitForURL('**/platform');
await page.goto('./platform/');
await expect(page).toHaveTitle('InvenTree');
await page.waitForURL('**/platform/');
await page
.getByRole('heading', { name: 'Welcome to your Dashboard,' })
.click();
await page.waitForTimeout(500);
// Open Spotlight with Keyboard Shortcut
await page.locator('body').press(`${systemKey}+k`);
await page.waitForTimeout(200);
await page
.getByRole('button', { name: 'Dashboard Go to the InvenTree dashboard' })
.click();
await page
.locator('div')
.filter({ hasText: /^Dashboard$/ })
.click();
await page.waitForURL('**/platform/dashboard');
// Open Spotlight with Button
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page.getByRole('button', { name: 'Home Go to the home page' }).click();
await page
.getByRole('heading', { name: 'Welcome to your Dashboard,' })
.click();
await page.waitForURL('**/platform');
// Open Spotlight with Keyboard Shortcut and Search
await page.locator('body').press(`${systemKey}+k`);
await page.waitForTimeout(200);
await page.getByPlaceholder('Search...').fill('Dashboard');
await page.getByPlaceholder('Search...').press('Tab');
await page.getByPlaceholder('Search...').press('Enter');
await page.waitForURL('**/platform/dashboard');
});
test('PUI - Quick Command - no keys', async ({ page }) => {
await page.goto('./platform/');
await expect(page).toHaveTitle('InvenTree');
await page.waitForURL('**/platform/');
await page.getByLabel('username').fill('allaccess');
await page.getByLabel('password').fill('nolimits');
await page.getByRole('button', { name: 'Log in' }).click();
await page.waitForURL('**/platform');
await expect(page).toHaveTitle('InvenTree');
await page.waitForURL('**/platform');
// wait for the page to load - 0.5s
await page.waitForTimeout(500);
// Open Spotlight with Button
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page.getByRole('button', { name: 'Home Go to the home page' }).click();
await page
.getByRole('heading', { name: 'Welcome to your Dashboard,' })
.click();
await page.waitForURL('**/platform');
// Use navigation menu
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page
.getByRole('button', { name: 'Open Navigation Open the main' })
.click();
// assert the nav headers are visible
await page.getByRole('heading', { name: 'Navigation' }).waitFor();
await page.getByRole('heading', { name: 'Pages' }).waitFor();
await page.getByRole('heading', { name: 'Documentation' }).waitFor();
await page.getByRole('heading', { name: 'About' }).waitFor();
await page.keyboard.press('Escape');
// use server info
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page
.getByRole('button', {
name: 'Server Information About this Inventree instance'
})
.click();
await page.getByRole('cell', { name: 'Instance Name' }).waitFor();
await page.getByRole('button', { name: 'Dismiss' }).click();
await page.waitForURL('**/platform');
// use license info
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page
.getByRole('button', {
name: 'License Information Licenses for dependencies of the service'
})
.click();
await page.getByText('License Information').first().waitFor();
await page.getByRole('tab', { name: 'backend Packages' }).waitFor();
await page.getByLabel('License Information').getByRole('button').click();
// use about
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page
.getByRole('button', { name: 'About InvenTree About the InvenTree org' })
.click();
await page.getByText('This information is only').waitFor();
await page.getByLabel('About InvenTree').getByRole('button').click();
// use documentation
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page
.getByRole('button', {
name: 'Documentation Visit the documentation to learn more about InvenTree'
})
.click();
await page.waitForURL('https://docs.inventree.org/**');
// Test addition of new actions
await page.goto('./platform/playground');
await page
.locator('div')
.filter({ hasText: /^Playground$/ })
.waitFor();
await page.getByRole('button', { name: 'Spotlight actions' }).click();
await page.getByRole('button', { name: 'Register extra actions' }).click();
await page.getByPlaceholder('Search...').fill('secret');
await page.getByRole('button', { name: 'Secret action It was' }).click();
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page.getByPlaceholder('Search...').fill('Another secret action');
await page
.getByRole('button', {
name: 'Another secret action You can register multiple actions with just one command'
})
.click();
await page.getByRole('tab', { name: 'Home' }).click();
await page.getByRole('button', { name: 'Open spotlight' }).click();
await page.getByPlaceholder('Search...').fill('secret');
await page.getByText('Nothing found...').click();
});

View File

@ -1156,6 +1156,13 @@
"@mantine/utils" "6.0.21" "@mantine/utils" "6.0.21"
react-transition-group "4.4.2" react-transition-group "4.4.2"
"@mantine/spotlight@<7":
version "6.0.21"
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-6.0.21.tgz#98f507bd3429fee1f2b57ad5ef9f88d1d8d8ff32"
integrity sha512-xJqF2Vpn8s6I4mSF+iCi7IzqL8iaqbvq0RcYlF1usLZYW2HrArX31s1r11DmzqM1PIuBQUhquW8jUXx/MZy3oA==
dependencies:
"@mantine/utils" "6.0.21"
"@mantine/styles@6.0.21": "@mantine/styles@6.0.21":
version "6.0.21" version "6.0.21"
resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-6.0.21.tgz#8ea097fc76cbb3ed55f5cfd719d2f910aff5031b" resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-6.0.21.tgz#8ea097fc76cbb3ed55f5cfd719d2f910aff5031b"