diff --git a/ivette/src/frama-c/index.tsx b/ivette/src/frama-c/index.tsx index e80157c81d78d36f56587d4a7ca7543ae5ef5752..67192985b75380ba638c1d7e3c8ee8c8b5abb756 100644 --- a/ivette/src/frama-c/index.tsx +++ b/ivette/src/frama-c/index.tsx @@ -20,9 +20,9 @@ /* */ /* ************************************************************************ */ -/* --------------------------------------------------------------------------*/ -/* --- Frama-C Registry ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Frama-C Registry --- */ +/* -------------------------------------------------------------------------- */ import React from 'react'; import * as Ivette from 'ivette'; @@ -44,17 +44,27 @@ import * as Menu from 'frama-c/menu'; Menu.init(); -/* --------------------------------------------------------------------------*/ -/* --- Frama-C Kernel Groups ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Frama-C Tools --- */ +/* -------------------------------------------------------------------------- */ + +Ivette.registerSidebar({ + id: 'frama-c.globals', + label: 'AST', + children: <Globals /> +}); + +Ivette.registerToolbar({ id: 'frama-c.history', children: <History /> }); +Ivette.registerStatusbar({ id: 'frama-c.message', children: <Status /> }); + +/* -------------------------------------------------------------------------- */ +/* --- Frama-C Kernel Groups --- */ +/* -------------------------------------------------------------------------- */ Ivette.registerGroup({ id: 'frama-c.kernel', label: 'Frama-C Kernel', }, () => { - Ivette.registerSidebar({ id: 'frama-c.globals', children: <Globals /> }); - Ivette.registerToolbar({ id: 'frama-c.history', children: <History /> }); - Ivette.registerStatusbar({ id: 'frama-c.message', children: <Status /> }); Ivette.registerComponent({ id: 'frama-c.astinfo', label: 'Inspector', @@ -99,18 +109,18 @@ Ivette.registerGroup({ }); }); -/* --------------------------------------------------------------------------*/ -/* --- Frama-C Plug-ins Group ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Frama-C Plug-ins Group --- */ +/* -------------------------------------------------------------------------- */ Ivette.registerGroup({ id: 'frama-c.plugins', label: 'Frama-C Plug-ins', }); -/* --------------------------------------------------------------------------*/ -/* --- Frama-C Views ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Frama-C Views --- */ +/* -------------------------------------------------------------------------- */ Ivette.registerView({ id: 'source', @@ -142,4 +152,4 @@ Ivette.registerView({ ], }); -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/plugins/eva/images/index.d.ts b/ivette/src/frama-c/plugins/eva/images/index.d.ts index 6d273a0b4c045858a9096c9c85eb3413095308db..d3c9c86538519ab02ba80e8c8bf14788c2484549 100644 --- a/ivette/src/frama-c/plugins/eva/images/index.d.ts +++ b/ivette/src/frama-c/plugins/eva/images/index.d.ts @@ -21,3 +21,4 @@ /* ************************************************************************ */ declare module '*.svg'; +declare module '*.png'; diff --git a/ivette/src/ivette/index.tsx b/ivette/src/ivette/index.tsx index 625397e2e397e8abe9278ad11d9e943ce93633a4..f3169e20e94753f83e1944f991493416a16b6d94 100644 --- a/ivette/src/ivette/index.tsx +++ b/ivette/src/ivette/index.tsx @@ -20,9 +20,9 @@ /* */ /* ************************************************************************ */ -/* --------------------------------------------------------------------------*/ -/* --- Lab View Component ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Lab View Component --- */ +/* -------------------------------------------------------------------------- */ /** @packageDocumentation @@ -37,9 +37,9 @@ import { GridItem, GridHbox, GridVbox } from 'dome/layout/grids'; import * as Lab from 'ivette@lab'; import * as Ext from 'ivette@ext'; -/* --------------------------------------------------------------------------*/ -/* --- Items ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Items --- */ +/* -------------------------------------------------------------------------- */ export interface ItemProps { /** Identifier. */ @@ -57,9 +57,9 @@ export interface ContentProps extends ItemProps { children?: React.ReactNode; } -/* --------------------------------------------------------------------------*/ -/* --- Groups ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Groups --- */ +/* -------------------------------------------------------------------------- */ let GROUP: string | undefined; @@ -84,9 +84,9 @@ export function registerGroup(group: ItemProps, job?: () => void): void { } } -/* --------------------------------------------------------------------------*/ -/* --- View Layout ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- View Layout --- */ +/* -------------------------------------------------------------------------- */ /** Alternating V-split and H-split layouts. @@ -127,9 +127,9 @@ export function registerView(view: ViewLayoutProps): void { }); } -/* --------------------------------------------------------------------------*/ -/* --- Components ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Components --- */ +/* -------------------------------------------------------------------------- */ export interface ComponentProps extends ContentProps { /** Group attachment. */ @@ -177,9 +177,9 @@ export function TitleBar(props: TitleBarProps): JSX.Element | null { ); } -/* --------------------------------------------------------------------------*/ -/* --- Sidebar Panels ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Sidebar Panels --- */ +/* -------------------------------------------------------------------------- */ export interface ToolProps { id: string; @@ -187,21 +187,38 @@ export interface ToolProps { children?: React.ReactNode; } -export function registerSidebar(panel: ToolProps): void { - Ext.SIDEBAR.register(panel); -} +/** @ignore */ +export const TOOLBAR = new Ext.ElementRack<ToolProps>(); + +/** @ignore */ +export const STATUSBAR = new Ext.ElementRack<ToolProps>(); export function registerToolbar(tools: ToolProps): void { - Ext.TOOLBAR.register(tools); + TOOLBAR.register(tools); } export function registerStatusbar(status: ToolProps): void { - Ext.STATUSBAR.register(status); + STATUSBAR.register(status); +} + +/* -------------------------------------------------------------------------- */ +/* --- Sidebar --- */ +/* -------------------------------------------------------------------------- */ + +export interface SidebarProps extends ContentProps { + iconPath?: string; +} + +/** @ignore */ +export const SIDEBAR = new Ext.ElementRack<SidebarProps>(); + +export function registerSidebar(sidebar: SidebarProps): void { + SIDEBAR.register(sidebar); } -/* --------------------------------------------------------------------------*/ -/* --- Sandbox ---*/ -/* --------------------------------------------------------------------------*/ +/* -------------------------------------------------------------------------- */ +/* --- Sandbox --- */ +/* -------------------------------------------------------------------------- */ if (DEVEL) { registerGroup({ @@ -222,4 +239,4 @@ export function registerSandbox(props: ComponentProps): void { if (DEVEL) registerComponent({ ...props, group: 'sandbox' }); } -// -------------------------------------------------------------------------- +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx index c6a1f7276e0ced47a5254696419f6ab9623e29ce..e1b8817d674a33c6962b8a8b9403b5bd7b27e1ea 100644 --- a/ivette/src/renderer/Application.tsx +++ b/ivette/src/renderer/Application.tsx @@ -31,10 +31,11 @@ import * as Dome from 'dome'; import { Vfill } from 'dome/layout/boxes'; import { LSplit } from 'dome/layout/splitters'; import * as Toolbar from 'dome/frame/toolbars'; -import * as Sidebar from 'dome/frame/sidebars'; +import * as Sidebar from './Sidebar'; import * as Controller from './Controller'; -import * as Extensions from './Extensions'; import * as Laboratory from './Laboratory'; +import * as Ext from './Extensions'; +import { TOOLBAR, STATUSBAR } from 'ivette'; import * as IvettePrefs from 'ivette/prefs'; import * as Studia from 'frama-c/plugins/studia/studia'; import './loader'; @@ -51,6 +52,8 @@ export default function Application(): JSX.Element { Dome.useFlipSettings('frama-c.viewbar.unfold', true); Studia.useStudiaMode(); + const tools = Ext.useChildren(TOOLBAR); + const status = Ext.useChildren(STATUSBAR); return ( <Vfill> @@ -62,7 +65,7 @@ export default function Application(): JSX.Element { onClick={flipSidebar} /> <Controller.Control /> - <Extensions.Toolbar /> + <>{tools}</> <Toolbar.Filler /> <IvettePrefs.ThemeSwitchTool /> <IvettePrefs.FontTools /> @@ -75,10 +78,7 @@ export default function Application(): JSX.Element { /> </Toolbar.ToolBar> <LSplit settings="frama-c.sidebar.split" unfold={sidebar}> - <Sidebar.SideBar> - <div className="sidebar-ruler" /> - <Extensions.Sidebar /> - </Sidebar.SideBar> + <Sidebar.Panel /> <Laboratory.LabView customize={viewbar} settings="frama-c.labview" @@ -86,7 +86,7 @@ export default function Application(): JSX.Element { </LSplit> <Toolbar.ToolBar> <Controller.Status /> - <Extensions.Statusbar /> + <>{status}</> <Toolbar.Filler /> <Controller.Stats /> </Toolbar.ToolBar> diff --git a/ivette/src/renderer/Extensions.tsx b/ivette/src/renderer/Extensions.tsx index 4b8ec17ea0a59be97d2c5004b6c02102f2199569..3f4a16e55e2b68ddd64f3de494fbff748d4bf830 100644 --- a/ivette/src/renderer/Extensions.tsx +++ b/ivette/src/renderer/Extensions.tsx @@ -39,7 +39,7 @@ export interface ElementProps { children?: React.ReactNode; } -function byPanel(p: ElementProps, q: ElementProps): number { +function byRank(p: ElementProps, q: ElementProps): number { const rp = p.rank ?? 0; const rq = q.rank ?? 0; if (rp < rq) return -1; @@ -51,38 +51,38 @@ function byPanel(p: ElementProps, q: ElementProps): number { return 0; } -export class ElementRack { +export class ElementRack<A extends ElementProps> { private rank = 1; - private readonly items = new Map<string, ElementProps>(); + private readonly items = new Map<string, A>(); - register(elt: ElementProps): void { + register(elt: A): void { if (elt.rank === undefined) elt.rank = this.rank; this.rank++; this.items.set(elt.id, elt); UPDATED.emit(); } - render(): JSX.Element { - const panels: ElementProps[] = []; - this.items.forEach((p) => { if (p.children) { panels.push(p); } }); - const contents = panels.sort(byPanel).map((p) => p.children); - return <>{React.Children.toArray(contents)}</>; + getElements(): A[] { + const buffer: A[] = []; + this.items.forEach((p) => { if (p.children) { buffer.push(p); } }); + return buffer.sort(byRank); } } -export function useRack(E: ElementRack): JSX.Element { +export function useElements<A extends ElementProps>( + E: ElementRack<A> +): A[] { Dome.useUpdate(UPDATED); - return E.render(); + return E.getElements(); } -export const SIDEBAR = new ElementRack(); -export const TOOLBAR = new ElementRack(); -export const STATUSBAR = new ElementRack(); - -export function Sidebar(): JSX.Element { return useRack(SIDEBAR); } -export function Toolbar(): JSX.Element { return useRack(TOOLBAR); } -export function Statusbar(): JSX.Element { return useRack(STATUSBAR); } +export function useChildren<A extends ElementProps>( + E: ElementRack<A> +): React.ReactNode { + const elements = useElements(E); + return React.Children.toArray(elements.map((e) => e.children)); +} /* --------------------------------------------------------------------------*/ diff --git a/ivette/src/renderer/Sidebar.tsx b/ivette/src/renderer/Sidebar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8ac673ab020f27eef06be8137f4a7f58ea9823a6 --- /dev/null +++ b/ivette/src/renderer/Sidebar.tsx @@ -0,0 +1,133 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +// -------------------------------------------------------------------------- +// --- Sidebar Selector +// -------------------------------------------------------------------------- + +import React from 'react'; +import * as Dome from 'dome'; +import { SideBar } from 'dome/frame/sidebars'; +import { Catch } from 'dome/errors'; +import { classes } from 'dome/misc/utils'; +import { SidebarProps, SIDEBAR } from 'ivette'; +import * as Ext from './Extensions'; + +/* -------------------------------------------------------------------------- */ +/* --- SideBar Selector --- */ +/* -------------------------------------------------------------------------- */ + +interface SelectorProps extends SidebarProps { + selected: string; + setSelected: (item: string) => void; +} + +function Selector(props: SelectorProps): JSX.Element { + const { id, iconPath, selected, setSelected, label, title } = props; + const className = classes( + 'sidebar-selector', + 'dome-color-frame', + selected === id && 'sidebar-selector-selected', + ); + const onClick = React.useCallback(() => setSelected(id), [setSelected, id]); + const component = + iconPath + ? <img className="sidebar-selector-icon" src={iconPath} alt={label} /> + : <label className="sidebar-selector-label">{label}</label>; + return ( + <div className={className} title={title} onClick={onClick}> + {component} + </div> + ); +} + +/* -------------------------------------------------------------------------- */ +/* --- User Sidebar Wrapper --- */ +/* -------------------------------------------------------------------------- */ + +interface WrapperProps extends SidebarProps { + selected: string; +} + +function Wrapper(props: WrapperProps): JSX.Element { + const className = props.selected === props.id ? '' : 'dome-erased'; + + return ( + <SideBar className={className}> + <Catch label={props.id}> + {props.children} + </Catch> + </SideBar> + ); +} + +/* -------------------------------------------------------------------------- */ +/* --- SideBar Main Component --- */ +/* -------------------------------------------------------------------------- */ + +export function Panel(): JSX.Element { + const [selected, setSelected] = + Dome.useStringSettings('ivette.sidebar.selected'); + + const sidebars = Ext.useElements(SIDEBAR); + + // Ensures there is one selected sidebar + React.useEffect(() => { + if (sidebars.every((sb) => sb.id !== selected)) { + const first = sidebars[0]; + if (first) setSelected(first.id); + } + }, [sidebars, selected, setSelected]); + + const items = sidebars.map((sb) => ( + <Selector + key={sb.id} + selected={selected} + setSelected={setSelected} + {...sb} /> + )); + + const wrappers = sidebars.map((sb) => ( + <Wrapper + key={sb.id} + selected={selected} + {...sb} + /> + )); + + // Hide sidebar if only one of them + const selectorClasses = classes( + 'sidebar-items dome-color-frame', + sidebars.length <= 1 && 'dome-erased' + ); + + return ( + <div className="sidebar-ruler"> + <div className={selectorClasses}> + {items} + </div> + {wrappers} + </div> + ); +} + +// -------------------------------------------------------------------------- diff --git a/ivette/src/renderer/style.css b/ivette/src/renderer/style.css index 286da16776d598e6899fcd7aeae67733975d2609..902aef41f4182f12b2fc9d5bccfaee7edaf207db 100644 --- a/ivette/src/renderer/style.css +++ b/ivette/src/renderer/style.css @@ -3,10 +3,46 @@ /* -------------------------------------------------------------------------- */ .sidebar-ruler { - display: block; - visibility: hidden; - width: 80px; - height: 0px; + display: flex; + height: 100%; + overflow-x: hidden; + overflow-y: auto; +} + +.sidebar-items.dome-color-frame +{ + background: var(--background-intense) ; +} + +.sidebar-selector { + min-width: 35px; + text-align: center; +} + +.sidebar-selector-icon { + margin-top: 5px; + margin-left: 25%; + margin-right: 25%; + width: 25px; + height: 25px; +} + +.sidebar-selector-icon:hover { + background-color: var(--background-profound-hover); +} + +.sidebar-selector-label { + display: inline-block; + margin: 5px; + font-weight: bold; +} + +.sidebar-selector:hover { + background-color: var(--background-profound-hover); +} + +.sidebar-selector-selected { + background-color: var(--background-profound); } .component-info { diff --git a/ivette/src/sandbox/icons/file.png b/ivette/src/sandbox/icons/file.png new file mode 100644 index 0000000000000000000000000000000000000000..280d186f5685b01f90b9f88e076700de9a66ba80 Binary files /dev/null and b/ivette/src/sandbox/icons/file.png differ diff --git a/ivette/src/sandbox/icons/folder.png b/ivette/src/sandbox/icons/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..e765d59083f8e0621c21e72284086ce8cdec6637 Binary files /dev/null and b/ivette/src/sandbox/icons/folder.png differ diff --git a/ivette/src/sandbox/icons/function.png b/ivette/src/sandbox/icons/function.png new file mode 100644 index 0000000000000000000000000000000000000000..f8941398bd98bfabc073c639831e7ea42c30286a Binary files /dev/null and b/ivette/src/sandbox/icons/function.png differ diff --git a/ivette/src/sandbox/sidebar.tsx b/ivette/src/sandbox/sidebar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a78343eea5918364b624dd7f69f5452075080320 --- /dev/null +++ b/ivette/src/sandbox/sidebar.tsx @@ -0,0 +1,124 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + + +import React from 'react'; +import { DEVEL } from 'dome'; +import { Item, Section } from "dome/frame/sidebars"; +import * as Ivette from 'ivette'; +import file from './icons/file.png'; +import folder from './icons/folder.png'; + +/* -------------------------------------------------------------------------- */ +/* --- Mocking --- */ +/* -------------------------------------------------------------------------- */ + +if (DEVEL) { + + Ivette.registerSidebar({ + id: 'sandbox.sidebar.a', + label: 'SanA', + title: 'Sandbox Title A', + iconPath: folder, + children: + <> + <Section label='Section A.1'> + <Item label='Item A.1.1' /> + <Item label='Item A.1.2' /> + <Item label='Item A.1.3' /> + <Item label='Item A.1.4' /> + </Section> + <Section label='Section A.2'> + <Item label='Item A.2.1' /> + <Item label='Item A.2.2' /> + <Item label='Item A.2.3' /> + <Item label='Item A.2.4' /> + </Section> + </> + }); + + Ivette.registerSidebar({ + id: 'sandbox.sidebar.b', + label: 'SanB', + title: 'Sandbox Title B', + children: + <> + <Section label='Section B.1'> + <Item label='Item B.1.1' /> + <Item label='Item B.1.2' /> + <Item label='Item B.1.3' /> + <Item label='Item B.1.4' /> + </Section> + <Section label='Section B.2'> + <Item label='Item B.2.1' /> + <Item label='Item B.2.2' /> + <Item label='Item B.2.3' /> + <Item label='Item B.2.4' /> + </Section> + </> + }); + + Ivette.registerSidebar({ + id: 'sandbox.sidebar.c', + label: 'SanC', + title: 'Sandbox Title C', + children: + <> + <Section label='Section C.1'> + <Item label='Item C.1.1' /> + <Item label='Item C.1.2' /> + <Item label='Item C.1.3' /> + <Item label='Item C.1.4' /> + </Section> + <Section label='Section C.2'> + <Item label='Item C.2.1' /> + <Item label='Item C.2.2' /> + <Item label='Item C.2.3' /> + <Item label='Item C.2.4' /> + </Section> + </> + }); + + Ivette.registerSidebar({ + id: 'sandbox.sidebar.d', + label: 'SanD', + iconPath: file, + title: 'Sandbox Title D', + children: + <> + <Section label='Section D.1'> + <Item label='Item D.1.1' /> + <Item label='Item D.1.2' /> + <Item label='Item D.1.3' /> + <Item label='Item D.1.4' /> + </Section> + <Section label='Section D.2'> + <Item label='Item D.2.1' /> + <Item label='Item D.2.2' /> + <Item label='Item D.2.3' /> + <Item label='Item D.2.4' /> + </Section> + </> + }); + +} +/* -------------------------------------------------------------------------- */ diff --git a/ivette/tests/e2e/doubleBar.spec.ts b/ivette/tests/e2e/doubleBar.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..dad17a0267218583659b0ebe2dcef0aa96601d35 --- /dev/null +++ b/ivette/tests/e2e/doubleBar.spec.ts @@ -0,0 +1,37 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { test } from "@playwright/test"; +import * as e2eService from "../libs/e2eService"; + +test("testing double bar - 'functions' category", async () => { + const launchAppResult = await e2eService.launchApp( + e2eService.argsLaunchWithTestFile, + ); + const electronApp = launchAppResult.app; + const window = launchAppResult.page; + + await e2eService.testFileIsLoadedInDoubleBar(window); + + // Exit app. + await electronApp.close(); +}); diff --git a/ivette/tests/libs/e2eService.ts b/ivette/tests/libs/e2eService.ts index 1de42c07e52ebf15a202b03ec1c6b212a9447494..433ac959340e02a3c96783dce057e9b95211ce8d 100644 --- a/ivette/tests/libs/e2eService.ts +++ b/ivette/tests/libs/e2eService.ts @@ -106,3 +106,10 @@ export async function testFileIsLoaded(window: Page): Promise<void> { locs.getFunctionsSideBar(window).getByText("main", { exact: true }) ).toBeVisible(); } + +export async function testFileIsLoadedInDoubleBar(window: Page): Promise<void> { + // Check if the main function is visible in the functions view + await expect( + locs.getDoubleBarSecondary(window).getByText("main", { exact: true }) + ).toBeVisible(); +} diff --git a/ivette/tests/libs/locatorsUtil.ts b/ivette/tests/libs/locatorsUtil.ts index 7d57f5843c6e331941e9620a06509ea7602dc3f1..46fc9c810f909abc7f91e428c0a2446dc4c9ff3d 100644 --- a/ivette/tests/libs/locatorsUtil.ts +++ b/ivette/tests/libs/locatorsUtil.ts @@ -65,3 +65,17 @@ export function getFunctionsSideBar(window: Page): Locator { export function getServerStatusLabel(window: Page): Locator { return window.getByTitle("Server is running"); } + +/** + * Locator to access the primary section (categories) of the double bar + */ +export function getDoubleBarPrimary(window: Page): Locator { + return window.locator('.dome-xDoubleBar-primary'); +} + +/** + * Locator to access the secondary section (submenu) of the double bar + */ +export function getDoubleBarSecondary(window: Page): Locator { + return window.locator('dome-xSideBar-double-secondary'); +}