mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-07 09:13:04 +02:00
Put tools in a collapsible
This commit is contained in:
+140
-134
@@ -28,6 +28,7 @@ type ConversationRow = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type CollapseState = {
|
type CollapseState = {
|
||||||
|
tools: boolean;
|
||||||
favorites: boolean;
|
favorites: boolean;
|
||||||
channels: boolean;
|
channels: boolean;
|
||||||
contacts: boolean;
|
contacts: boolean;
|
||||||
@@ -37,6 +38,7 @@ type CollapseState = {
|
|||||||
const SIDEBAR_COLLAPSE_STATE_KEY = 'remoteterm-sidebar-collapse-state';
|
const SIDEBAR_COLLAPSE_STATE_KEY = 'remoteterm-sidebar-collapse-state';
|
||||||
|
|
||||||
const DEFAULT_COLLAPSE_STATE: CollapseState = {
|
const DEFAULT_COLLAPSE_STATE: CollapseState = {
|
||||||
|
tools: false,
|
||||||
favorites: false,
|
favorites: false,
|
||||||
channels: false,
|
channels: false,
|
||||||
contacts: false,
|
contacts: false,
|
||||||
@@ -49,6 +51,7 @@ function loadCollapsedState(): CollapseState {
|
|||||||
if (!raw) return DEFAULT_COLLAPSE_STATE;
|
if (!raw) return DEFAULT_COLLAPSE_STATE;
|
||||||
const parsed = JSON.parse(raw) as Partial<CollapseState>;
|
const parsed = JSON.parse(raw) as Partial<CollapseState>;
|
||||||
return {
|
return {
|
||||||
|
tools: parsed.tools ?? DEFAULT_COLLAPSE_STATE.tools,
|
||||||
favorites: parsed.favorites ?? DEFAULT_COLLAPSE_STATE.favorites,
|
favorites: parsed.favorites ?? DEFAULT_COLLAPSE_STATE.favorites,
|
||||||
channels: parsed.channels ?? DEFAULT_COLLAPSE_STATE.channels,
|
channels: parsed.channels ?? DEFAULT_COLLAPSE_STATE.channels,
|
||||||
contacts: parsed.contacts ?? DEFAULT_COLLAPSE_STATE.contacts,
|
contacts: parsed.contacts ?? DEFAULT_COLLAPSE_STATE.contacts,
|
||||||
@@ -100,6 +103,7 @@ export function Sidebar({
|
|||||||
const sortOrder = sortOrderProp;
|
const sortOrder = sortOrderProp;
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const initialCollapsedState = useMemo(loadCollapsedState, []);
|
const initialCollapsedState = useMemo(loadCollapsedState, []);
|
||||||
|
const [toolsCollapsed, setToolsCollapsed] = useState(initialCollapsedState.tools);
|
||||||
const [favoritesCollapsed, setFavoritesCollapsed] = useState(initialCollapsedState.favorites);
|
const [favoritesCollapsed, setFavoritesCollapsed] = useState(initialCollapsedState.favorites);
|
||||||
const [channelsCollapsed, setChannelsCollapsed] = useState(initialCollapsedState.channels);
|
const [channelsCollapsed, setChannelsCollapsed] = useState(initialCollapsedState.channels);
|
||||||
const [contactsCollapsed, setContactsCollapsed] = useState(initialCollapsedState.contacts);
|
const [contactsCollapsed, setContactsCollapsed] = useState(initialCollapsedState.contacts);
|
||||||
@@ -262,6 +266,7 @@ export function Sidebar({
|
|||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
if (!collapseSnapshotRef.current) {
|
if (!collapseSnapshotRef.current) {
|
||||||
collapseSnapshotRef.current = {
|
collapseSnapshotRef.current = {
|
||||||
|
tools: toolsCollapsed,
|
||||||
favorites: favoritesCollapsed,
|
favorites: favoritesCollapsed,
|
||||||
channels: channelsCollapsed,
|
channels: channelsCollapsed,
|
||||||
contacts: contactsCollapsed,
|
contacts: contactsCollapsed,
|
||||||
@@ -269,7 +274,14 @@ export function Sidebar({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (favoritesCollapsed || channelsCollapsed || contactsCollapsed || repeatersCollapsed) {
|
if (
|
||||||
|
toolsCollapsed ||
|
||||||
|
favoritesCollapsed ||
|
||||||
|
channelsCollapsed ||
|
||||||
|
contactsCollapsed ||
|
||||||
|
repeatersCollapsed
|
||||||
|
) {
|
||||||
|
setToolsCollapsed(false);
|
||||||
setFavoritesCollapsed(false);
|
setFavoritesCollapsed(false);
|
||||||
setChannelsCollapsed(false);
|
setChannelsCollapsed(false);
|
||||||
setContactsCollapsed(false);
|
setContactsCollapsed(false);
|
||||||
@@ -281,17 +293,26 @@ export function Sidebar({
|
|||||||
if (collapseSnapshotRef.current) {
|
if (collapseSnapshotRef.current) {
|
||||||
const prev = collapseSnapshotRef.current;
|
const prev = collapseSnapshotRef.current;
|
||||||
collapseSnapshotRef.current = null;
|
collapseSnapshotRef.current = null;
|
||||||
|
setToolsCollapsed(prev.tools);
|
||||||
setFavoritesCollapsed(prev.favorites);
|
setFavoritesCollapsed(prev.favorites);
|
||||||
setChannelsCollapsed(prev.channels);
|
setChannelsCollapsed(prev.channels);
|
||||||
setContactsCollapsed(prev.contacts);
|
setContactsCollapsed(prev.contacts);
|
||||||
setRepeatersCollapsed(prev.repeaters);
|
setRepeatersCollapsed(prev.repeaters);
|
||||||
}
|
}
|
||||||
}, [isSearching, favoritesCollapsed, channelsCollapsed, contactsCollapsed, repeatersCollapsed]);
|
}, [
|
||||||
|
isSearching,
|
||||||
|
toolsCollapsed,
|
||||||
|
favoritesCollapsed,
|
||||||
|
channelsCollapsed,
|
||||||
|
contactsCollapsed,
|
||||||
|
repeatersCollapsed,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSearching) return;
|
if (isSearching) return;
|
||||||
|
|
||||||
const state: CollapseState = {
|
const state: CollapseState = {
|
||||||
|
tools: toolsCollapsed,
|
||||||
favorites: favoritesCollapsed,
|
favorites: favoritesCollapsed,
|
||||||
channels: channelsCollapsed,
|
channels: channelsCollapsed,
|
||||||
contacts: contactsCollapsed,
|
contacts: contactsCollapsed,
|
||||||
@@ -303,7 +324,14 @@ export function Sidebar({
|
|||||||
} catch {
|
} catch {
|
||||||
// Ignore localStorage write failures (e.g., disabled storage)
|
// Ignore localStorage write failures (e.g., disabled storage)
|
||||||
}
|
}
|
||||||
}, [isSearching, favoritesCollapsed, channelsCollapsed, contactsCollapsed, repeatersCollapsed]);
|
}, [
|
||||||
|
isSearching,
|
||||||
|
toolsCollapsed,
|
||||||
|
favoritesCollapsed,
|
||||||
|
channelsCollapsed,
|
||||||
|
contactsCollapsed,
|
||||||
|
repeatersCollapsed,
|
||||||
|
]);
|
||||||
|
|
||||||
// Separate favorites from regular items, and build combined favorites list
|
// Separate favorites from regular items, and build combined favorites list
|
||||||
const { favoriteItems, nonFavoriteChannels, nonFavoriteContacts, nonFavoriteRepeaters } =
|
const { favoriteItems, nonFavoriteChannels, nonFavoriteContacts, nonFavoriteRepeaters } =
|
||||||
@@ -422,6 +450,38 @@ export function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderSidebarActionRow = ({
|
||||||
|
key,
|
||||||
|
active = false,
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
key: string;
|
||||||
|
active?: boolean;
|
||||||
|
icon: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className={cn(
|
||||||
|
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||||
|
active && 'bg-accent border-l-primary'
|
||||||
|
)}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-current={active ? 'page' : undefined}
|
||||||
|
onKeyDown={handleKeyboardActivate}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground text-xs" aria-hidden="true">
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
<span className="flex-1 truncate text-muted-foreground">{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const getSectionUnreadCount = (rows: ConversationRow[]): number =>
|
const getSectionUnreadCount = (rows: ConversationRow[]): number =>
|
||||||
rows.reduce((total, row) => total + row.unreadCount, 0);
|
rows.reduce((total, row) => total + row.unreadCount, 0);
|
||||||
|
|
||||||
@@ -438,6 +498,77 @@ export function Sidebar({
|
|||||||
const channelsUnreadCount = getSectionUnreadCount(channelRows);
|
const channelsUnreadCount = getSectionUnreadCount(channelRows);
|
||||||
const contactsUnreadCount = getSectionUnreadCount(contactRows);
|
const contactsUnreadCount = getSectionUnreadCount(contactRows);
|
||||||
const repeatersUnreadCount = getSectionUnreadCount(repeaterRows);
|
const repeatersUnreadCount = getSectionUnreadCount(repeaterRows);
|
||||||
|
const toolRows = !query
|
||||||
|
? [
|
||||||
|
renderSidebarActionRow({
|
||||||
|
key: 'tool-raw',
|
||||||
|
active: isActive('raw', 'raw'),
|
||||||
|
icon: '📡',
|
||||||
|
label: 'Packet Feed',
|
||||||
|
onClick: () =>
|
||||||
|
handleSelectConversation({
|
||||||
|
type: 'raw',
|
||||||
|
id: 'raw',
|
||||||
|
name: 'Raw Packet Feed',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
renderSidebarActionRow({
|
||||||
|
key: 'tool-map',
|
||||||
|
active: isActive('map', 'map'),
|
||||||
|
icon: '🗺️',
|
||||||
|
label: 'Node Map',
|
||||||
|
onClick: () =>
|
||||||
|
handleSelectConversation({
|
||||||
|
type: 'map',
|
||||||
|
id: 'map',
|
||||||
|
name: 'Node Map',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
renderSidebarActionRow({
|
||||||
|
key: 'tool-visualizer',
|
||||||
|
active: isActive('visualizer', 'visualizer'),
|
||||||
|
icon: '✨',
|
||||||
|
label: 'Mesh Visualizer',
|
||||||
|
onClick: () =>
|
||||||
|
handleSelectConversation({
|
||||||
|
type: 'visualizer',
|
||||||
|
id: 'visualizer',
|
||||||
|
name: 'Mesh Visualizer',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
renderSidebarActionRow({
|
||||||
|
key: 'tool-search',
|
||||||
|
active: isActive('search', 'search'),
|
||||||
|
icon: '🔍',
|
||||||
|
label: 'Message Search',
|
||||||
|
onClick: () =>
|
||||||
|
handleSelectConversation({
|
||||||
|
type: 'search',
|
||||||
|
id: 'search',
|
||||||
|
name: 'Message Search',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
renderSidebarActionRow({
|
||||||
|
key: 'tool-cracker',
|
||||||
|
active: showCracker,
|
||||||
|
icon: '🔓',
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
{showCracker ? 'Hide' : 'Show'} Room Finder
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'ml-1 text-[11px]',
|
||||||
|
crackerRunning ? 'text-primary' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
({crackerRunning ? 'running' : 'idle'})
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onClick: onToggleCracker,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
const renderSectionHeader = (
|
const renderSectionHeader = (
|
||||||
title: string,
|
title: string,
|
||||||
@@ -538,137 +669,12 @@ export function Sidebar({
|
|||||||
|
|
||||||
{/* List */}
|
{/* List */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{/* Raw Packet Feed */}
|
{/* Tools */}
|
||||||
{!query && (
|
{toolRows.length > 0 && (
|
||||||
<div
|
<>
|
||||||
className={cn(
|
{renderSectionHeader('Tools', toolsCollapsed, () => setToolsCollapsed((prev) => !prev))}
|
||||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
{(isSearching || !toolsCollapsed) && toolRows}
|
||||||
isActive('raw', 'raw') && 'bg-accent border-l-primary'
|
</>
|
||||||
)}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-current={isActive('raw', 'raw') ? 'page' : undefined}
|
|
||||||
onKeyDown={handleKeyboardActivate}
|
|
||||||
onClick={() =>
|
|
||||||
handleSelectConversation({
|
|
||||||
type: 'raw',
|
|
||||||
id: 'raw',
|
|
||||||
name: 'Raw Packet Feed',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="text-muted-foreground text-xs" aria-hidden="true">
|
|
||||||
📡
|
|
||||||
</span>
|
|
||||||
<span className="flex-1 truncate text-muted-foreground">Packet Feed</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Node Map */}
|
|
||||||
{!query && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
||||||
isActive('map', 'map') && 'bg-accent border-l-primary'
|
|
||||||
)}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-current={isActive('map', 'map') ? 'page' : undefined}
|
|
||||||
onKeyDown={handleKeyboardActivate}
|
|
||||||
onClick={() =>
|
|
||||||
handleSelectConversation({
|
|
||||||
type: 'map',
|
|
||||||
id: 'map',
|
|
||||||
name: 'Node Map',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="text-muted-foreground text-xs" aria-hidden="true">
|
|
||||||
🗺️
|
|
||||||
</span>
|
|
||||||
<span className="flex-1 truncate text-muted-foreground">Node Map</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Mesh Visualizer */}
|
|
||||||
{!query && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
||||||
isActive('visualizer', 'visualizer') && 'bg-accent border-l-primary'
|
|
||||||
)}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-current={isActive('visualizer', 'visualizer') ? 'page' : undefined}
|
|
||||||
onKeyDown={handleKeyboardActivate}
|
|
||||||
onClick={() =>
|
|
||||||
handleSelectConversation({
|
|
||||||
type: 'visualizer',
|
|
||||||
id: 'visualizer',
|
|
||||||
name: 'Mesh Visualizer',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="text-muted-foreground text-xs" aria-hidden="true">
|
|
||||||
✨
|
|
||||||
</span>
|
|
||||||
<span className="flex-1 truncate text-muted-foreground">Mesh Visualizer</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Message Search */}
|
|
||||||
{!query && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
||||||
isActive('search', 'search') && 'bg-accent border-l-primary'
|
|
||||||
)}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-current={isActive('search', 'search') ? 'page' : undefined}
|
|
||||||
onKeyDown={handleKeyboardActivate}
|
|
||||||
onClick={() =>
|
|
||||||
handleSelectConversation({
|
|
||||||
type: 'search',
|
|
||||||
id: 'search',
|
|
||||||
name: 'Message Search',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="text-muted-foreground text-xs" aria-hidden="true">
|
|
||||||
🔍
|
|
||||||
</span>
|
|
||||||
<span className="flex-1 truncate text-muted-foreground">Message Search</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Cracker Toggle */}
|
|
||||||
{!query && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
||||||
showCracker && 'bg-accent border-l-primary'
|
|
||||||
)}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={handleKeyboardActivate}
|
|
||||||
onClick={onToggleCracker}
|
|
||||||
>
|
|
||||||
<span className="text-muted-foreground text-xs" aria-hidden="true">
|
|
||||||
🔓
|
|
||||||
</span>
|
|
||||||
<span className="flex-1 truncate text-muted-foreground">
|
|
||||||
{showCracker ? 'Hide' : 'Show'} Room Finder
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'ml-1 text-[11px]',
|
|
||||||
crackerRunning ? 'text-primary' : 'text-muted-foreground'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
({crackerRunning ? 'running' : 'idle'})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mark All Read */}
|
{/* Mark All Read */}
|
||||||
|
|||||||
@@ -104,9 +104,11 @@ describe('Sidebar section summaries', () => {
|
|||||||
it('expands collapsed sections during search and restores collapse state after clearing search', async () => {
|
it('expands collapsed sections during search and restores collapse state after clearing search', async () => {
|
||||||
const { opsChannel, aliceName } = renderSidebar();
|
const { opsChannel, aliceName } = renderSidebar();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /Tools/i }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: /Channels/i }));
|
fireEvent.click(screen.getByRole('button', { name: /Channels/i }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: /Contacts/i }));
|
fireEvent.click(screen.getByRole('button', { name: /Contacts/i }));
|
||||||
|
|
||||||
|
expect(screen.queryByText('Packet Feed')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
||||||
|
|
||||||
@@ -120,6 +122,7 @@ describe('Sidebar section summaries', () => {
|
|||||||
fireEvent.change(search, { target: { value: '' } });
|
fireEvent.change(search, { target: { value: '' } });
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Packet Feed')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -128,15 +131,18 @@ describe('Sidebar section summaries', () => {
|
|||||||
it('persists collapsed section state across unmount and remount', () => {
|
it('persists collapsed section state across unmount and remount', () => {
|
||||||
const { opsChannel, aliceName, unmount } = renderSidebar();
|
const { opsChannel, aliceName, unmount } = renderSidebar();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /Tools/i }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: /Channels/i }));
|
fireEvent.click(screen.getByRole('button', { name: /Channels/i }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: /Contacts/i }));
|
fireEvent.click(screen.getByRole('button', { name: /Contacts/i }));
|
||||||
|
|
||||||
|
expect(screen.queryByText('Packet Feed')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
renderSidebar();
|
renderSidebar();
|
||||||
|
|
||||||
|
expect(screen.queryByText('Packet Feed')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user