mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 14:14:17 +00:00
feat: V2 ontime menu (#259)
This commit is contained in:
@@ -1,116 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { IconButton, Image } from '@chakra-ui/react';
|
|
||||||
import { IoContract } from '@react-icons/all-files/io5/IoContract';
|
|
||||||
import { IoExpand } from '@react-icons/all-files/io5/IoExpand';
|
|
||||||
import { IoSync } from '@react-icons/all-files/io5/IoSync';
|
|
||||||
import navlogo from 'assets/images/logos/LOGO-72.png';
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { useAtom } from 'jotai';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { mirrorViewersAtom } from '../../atoms/ViewerSettings';
|
|
||||||
import useFullscreen from '../../hooks/useFullscreen';
|
|
||||||
|
|
||||||
import navigatorConstants from './navigatorConstants';
|
|
||||||
|
|
||||||
import style from './NavLogo.module.scss';
|
|
||||||
|
|
||||||
/* Styling for action buttons */
|
|
||||||
const navButtonStyle = {
|
|
||||||
size: 'md',
|
|
||||||
variant: 'outline',
|
|
||||||
colorScheme: 'whiteAlpha',
|
|
||||||
_hover: { bg: '#ebedf0', color: '#333' },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function NavLogo(props) {
|
|
||||||
const { isHidden } = props;
|
|
||||||
const [showNav, setShowNav] = useState(false);
|
|
||||||
const { isFullScreen, toggleFullScreen } = useFullscreen();
|
|
||||||
const [isMirrored, setMirrored] = useAtom(mirrorViewersAtom);
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
setShowNav((prev) => !prev);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle keyboard shortcuts
|
|
||||||
const handleKeyPress = useCallback((e) => {
|
|
||||||
// handle held key
|
|
||||||
if (e.repeat) return;
|
|
||||||
// Space bar
|
|
||||||
if (e.keyCode === 32) {
|
|
||||||
setShowNav((s) => !s);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// attach the event listener
|
|
||||||
document.addEventListener('keydown', handleKeyPress);
|
|
||||||
|
|
||||||
// remove the event listener
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keydown', handleKeyPress);
|
|
||||||
};
|
|
||||||
}, [handleKeyPress]);
|
|
||||||
|
|
||||||
const baseOpacity = isHidden ? 0 : 0.5;
|
|
||||||
const tabProps = {
|
|
||||||
className: style.navItem,
|
|
||||||
tabIndex: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: showNav ? 0.5 : baseOpacity }}
|
|
||||||
whileHover={{ opacity: 1 }}
|
|
||||||
className={style.navContainer}
|
|
||||||
data-testid='nav-logo'
|
|
||||||
>
|
|
||||||
<Image alt='' src={navlogo} className={style.logo} onClick={handleClick} />
|
|
||||||
<AnimatePresence>
|
|
||||||
{showNav && (
|
|
||||||
<>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scaleX: 0 }}
|
|
||||||
animate={{ opacity: 1, scaleX: 1 }}
|
|
||||||
whileInView={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0, scaleX: 0 }}
|
|
||||||
className={style.actions}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
aria-label='Mirror screen'
|
|
||||||
icon={<IoSync />}
|
|
||||||
onClick={() => setMirrored((prev) => !prev)}
|
|
||||||
{...navButtonStyle}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
aria-label='Toggle Fullscreen'
|
|
||||||
icon={isFullScreen ? <IoContract /> : <IoExpand />}
|
|
||||||
onClick={toggleFullScreen}
|
|
||||||
{...navButtonStyle}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0, y: -50 }}
|
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
||||||
whileInView={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0, scaleY: 0, y: -50 }}
|
|
||||||
className={style.nav}
|
|
||||||
>
|
|
||||||
{navigatorConstants.map((route) => (
|
|
||||||
<Link to={route.url} key={route.url} {...tabProps}>
|
|
||||||
{route.label}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
NavLogo.propTypes = {
|
|
||||||
isHidden: PropTypes.bool,
|
|
||||||
};
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
@use '../../../theme/main' as *;
|
|
||||||
|
|
||||||
.navContainer {
|
|
||||||
position: absolute;
|
|
||||||
right: 2vw;
|
|
||||||
top: 1vw;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
font-size: max(1vw, 16px);
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: max(3vw, 32px);
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav,
|
|
||||||
.showNav {
|
|
||||||
margin-top: 2vh;
|
|
||||||
gap: 2vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.navItem {
|
|
||||||
background-color: $bg-overlay;
|
|
||||||
padding: 0.5vh 1vw;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: $text-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0.5vw;
|
|
||||||
right: 75px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
$action-text-color: #87A3EF;
|
||||||
|
$border-color: rgba(white, 0.1);
|
||||||
|
$menu-bg: #202020;
|
||||||
|
$menu-hover-bg: #101010;
|
||||||
|
$menu-focus-bg: #181818;
|
||||||
|
$menu-box-shadow: rgba(0, 0, 0, 0.15) 0 3px 3px 0;
|
||||||
|
|
||||||
|
$icon-color: #f6f6f6;
|
||||||
|
$button-bg: #303030;
|
||||||
|
$button-size: 48px;
|
||||||
|
|
||||||
|
$ontime-pink: #ff7597;
|
||||||
|
|
||||||
|
// Todo: remove important once style is retired
|
||||||
|
a {
|
||||||
|
&::after {
|
||||||
|
content: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navButton {
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
left: 0.5em;
|
||||||
|
top: 0.5em;
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
opacity: 1;
|
||||||
|
font-size: 24px;
|
||||||
|
color: $icon-color;
|
||||||
|
background-color: $button-bg;
|
||||||
|
width: $button-size;
|
||||||
|
height: $button-size;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuContainer {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
height: fit-content;
|
||||||
|
position: absolute;
|
||||||
|
background-color: $menu-bg;
|
||||||
|
min-width: 200px;
|
||||||
|
border-radius: 0 0 24px 0;
|
||||||
|
border-right: 1px solid $border-color;
|
||||||
|
|
||||||
|
box-shadow: $menu-box-shadow;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonsContainer {
|
||||||
|
margin-top: calc(56px + 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: $action-text-color;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $menu-hover-bg;
|
||||||
|
color: $ontime-pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
background-color: $menu-focus-bg;
|
||||||
|
border-left: 2px solid $action-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
background-color: $menu-hover-bg;
|
||||||
|
border-left: 4px solid $action-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
display: inline-block;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border-color: $border-color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { KeyboardEvent, useEffect, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { IoApps } from '@react-icons/all-files/io5/IoApps';
|
||||||
|
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||||
|
import { IoContract } from '@react-icons/all-files/io5/IoContract';
|
||||||
|
import { IoExpand } from '@react-icons/all-files/io5/IoExpand';
|
||||||
|
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
|
import { navigatorConstants } from '../../../viewerConfig';
|
||||||
|
import { mirrorViewersAtom } from '../../atoms/ViewerSettings';
|
||||||
|
import useFullscreen from '../../hooks/useFullscreen';
|
||||||
|
import { useKeyDown } from '../../hooks/useKeyDown';
|
||||||
|
|
||||||
|
import style from './NavigationMenu.module.scss';
|
||||||
|
|
||||||
|
// Todo: restyle _main.scss links
|
||||||
|
export default function NavigationMenu() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const { isFullScreen, toggleFullScreen } = useFullscreen();
|
||||||
|
const [isMirrored, setMirrored] = useAtom(mirrorViewersAtom);
|
||||||
|
const [showButton, setShowButton] = useState(false);
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
|
const toggleMenu = () => setShowMenu((prev) => !prev);
|
||||||
|
useKeyDown(toggleMenu, ' ');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let fadeOut: NodeJS.Timeout | null = null;
|
||||||
|
const setShowMenuTrue = () => {
|
||||||
|
setShowButton(true);
|
||||||
|
if (fadeOut) {
|
||||||
|
clearTimeout(fadeOut);
|
||||||
|
}
|
||||||
|
fadeOut = setTimeout(() => setShowButton(false), 3000);
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', setShowMenuTrue);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousemove', setShowMenuTrue);
|
||||||
|
if (fadeOut) {
|
||||||
|
clearTimeout(fadeOut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isKeyEnter = (event: KeyboardEvent<HTMLDivElement>) => event.key === 'Enter';
|
||||||
|
const handleFullscreen = () => toggleFullScreen();
|
||||||
|
const handleMirror = () => setMirrored((prev) => !prev);
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div id='navigation-menu-portal' className={isMirrored ? style.mirror : ''}>
|
||||||
|
<button
|
||||||
|
onClick={toggleMenu}
|
||||||
|
aria-label='toggle menu'
|
||||||
|
className={`${style.navButton} ${!showButton && !showMenu ? style.hidden : ''}`}
|
||||||
|
>
|
||||||
|
<IoApps />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showMenu && (
|
||||||
|
<div className={style.menuContainer} data-testid='navigation-menu'>
|
||||||
|
<div className={style.buttonsContainer}>
|
||||||
|
<div
|
||||||
|
className={style.link}
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={handleFullscreen}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
isKeyEnter(event) && handleFullscreen();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Toggle Fullscreen
|
||||||
|
{isFullScreen ? <IoContract /> : <IoExpand />}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={style.link}
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={handleMirror}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
isKeyEnter(event) && handleMirror();
|
||||||
|
}}>
|
||||||
|
Mirror Screen
|
||||||
|
<IoSwapVertical />
|
||||||
|
</div>
|
||||||
|
{/*<div className={style.link} tabIndex={0}>*/}
|
||||||
|
{/* Rename Client*/}
|
||||||
|
{/*</div>*/}
|
||||||
|
</div>
|
||||||
|
<hr className={style.separator} />
|
||||||
|
{navigatorConstants.map((route) => (
|
||||||
|
<Link
|
||||||
|
key={route.url}
|
||||||
|
to={route.url}
|
||||||
|
className={`${style.link} ${route.url === location.pathname ? style.current : undefined}`}
|
||||||
|
tabIndex={0}>
|
||||||
|
{route.label}
|
||||||
|
<IoArrowUp className={style.linkIcon} />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>, document.body);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useKeyDown = (callback: () => void, targetKey: string) => {
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
const targetKeyPressed = event.key === targetKey && !event.repeat;
|
||||||
|
if (targetKeyPressed) {
|
||||||
|
event.preventDefault();
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
|
};
|
||||||
|
}, [onKeyDown]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
|
exports[`cx() > ignores falsy values 1`] = `""`;
|
||||||
|
|
||||||
|
exports[`cx() > merges styles 1`] = `"_test_98a1e0 _another_98a1e0"`;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.test {}
|
||||||
|
.another {}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { cx } from '../styleUtils';
|
||||||
|
|
||||||
|
import style from './styleUtils.module.scss';
|
||||||
|
|
||||||
|
describe('cx()', () => {
|
||||||
|
test('merges styles', () => {
|
||||||
|
const merged = cx([style.test, style.another]);
|
||||||
|
expect(merged).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('ignores falsy values', () => {
|
||||||
|
const falsyStuff = false;
|
||||||
|
const merged = cx([undefined, false, 0, null, falsyStuff ? style.test : null]);
|
||||||
|
console.log(merged)
|
||||||
|
expect(merged).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,4 +26,4 @@ export const getAccessibleColour = (bgColour: string): ColourCombination => {
|
|||||||
* @description Creates a list of classnames from array of css module conditions
|
* @description Creates a list of classnames from array of css module conditions
|
||||||
* @param classNames - css modules objects
|
* @param classNames - css modules objects
|
||||||
*/
|
*/
|
||||||
export const cx = (...classNames: any[]) => classNames.filter(Boolean).join(" ")
|
export const cx = (classNames: any[]) => classNames.filter(Boolean).join(" ");
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import NavLogo from 'common/components/nav/NavLogo';
|
|
||||||
import TitleSide from 'common/components/title-side/TitleSide';
|
import TitleSide from 'common/components/title-side/TitleSide';
|
||||||
import { formatDisplay } from 'common/utils/dateConfig';
|
import { formatDisplay } from 'common/utils/dateConfig';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
@@ -9,6 +8,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import Paginator from '../../../common/components/paginator/Paginator';
|
import Paginator from '../../../common/components/paginator/Paginator';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
||||||
@@ -62,7 +62,7 @@ export default function Backstage(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
||||||
<NavLogo />
|
<NavigationMenu />
|
||||||
|
|
||||||
<div className='event-title'>{general.title}</div>
|
<div className='event-title'>{general.title}</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useAtom } from 'jotai';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManaget.type';
|
import { TimeManagerType } from '../../../common/models/TimeManaget.type';
|
||||||
import { ViewSettingsType } from '../../../common/models/ViewSettings.type';
|
import { ViewSettingsType } from '../../../common/models/ViewSettings.type';
|
||||||
@@ -120,9 +120,6 @@ export default function Clock(props: ClockProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideNav = searchParams.get('hidenav');
|
|
||||||
userOptions.hideNav = Boolean(hideNav);
|
|
||||||
|
|
||||||
const clock = formatTime(time.clock, formatOptions);
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
const clean = clock.replace('/:/g', '');
|
const clean = clock.replace('/:/g', '');
|
||||||
|
|
||||||
@@ -137,7 +134,7 @@ export default function Clock(props: ClockProps) {
|
|||||||
}}
|
}}
|
||||||
data-testid='clock-view'
|
data-testid='clock-view'
|
||||||
>
|
>
|
||||||
{!userOptions?.hideNav && <NavLogo />}
|
<NavigationMenu />
|
||||||
<div
|
<div
|
||||||
className='clock'
|
className='clock'
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import Empty from '../../../common/components/state/Empty';
|
import Empty from '../../../common/components/state/Empty';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||||
@@ -94,7 +94,7 @@ export default function Countdown(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
|
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
|
||||||
<NavLogo />
|
<NavigationMenu />
|
||||||
{follow === null ? (
|
{follow === null ? (
|
||||||
<div className='event-select' data-testid='countdown-select'>
|
<div className='event-select' data-testid='countdown-select'>
|
||||||
<span className='event-select__title'>Select an event to follow</span>
|
<span className='event-select__title'>Select an event to follow</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
|
||||||
import NavLogo from "../../../common/components/nav/NavLogo";
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
|
|
||||||
import './LowerClean.scss';
|
import './LowerClean.scss';
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export default function LowerClean(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<NavLogo isHidden />
|
<NavigationMenu />
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showLower && (
|
{showLower && (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
|
||||||
import NavLogo from "../../../common/components/nav/NavLogo";
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
|
|
||||||
import './LowerLines.scss';
|
import './LowerLines.scss';
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ export default function LowerLines(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<NavLogo isHidden />
|
<NavigationMenu />
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showLower && (
|
{showLower && (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useAtom } from 'jotai';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { PresenterMessageType } from '../../../common/models/PresenterMessage.type';
|
import { PresenterMessageType } from '../../../common/models/PresenterMessage.type';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManaget.type';
|
import { TimeManagerType } from '../../../common/models/TimeManaget.type';
|
||||||
@@ -117,9 +117,6 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideNav = searchParams.get('hidenav');
|
|
||||||
userOptions.hideNav = Boolean(hideNav);
|
|
||||||
|
|
||||||
const hideOvertime = searchParams.get('hideovertime');
|
const hideOvertime = searchParams.get('hideovertime');
|
||||||
userOptions.hideOvertime = Boolean(hideOvertime);
|
userOptions.hideOvertime = Boolean(hideOvertime);
|
||||||
|
|
||||||
@@ -144,6 +141,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
}}
|
}}
|
||||||
data-testid='minimal-timer'
|
data-testid='minimal-timer'
|
||||||
>
|
>
|
||||||
|
<NavigationMenu />
|
||||||
{!hideMessagesOverlay && (
|
{!hideMessagesOverlay && (
|
||||||
<div
|
<div
|
||||||
className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}
|
className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}
|
||||||
@@ -151,7 +149,6 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
<div className='message'>{pres.text}</div>
|
<div className='message'>{pres.text}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!userOptions?.hideNav && <NavLogo />}
|
|
||||||
<div
|
<div
|
||||||
className={`timer ${!isPlaying ? 'timer--paused' : ''} ${
|
className={`timer ${!isPlaying ? 'timer--paused' : ''} ${
|
||||||
showFinished ? 'timer--finished' : ''
|
showFinished ? 'timer--finished' : ''
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import { ReactComponent as Emptyimage } from 'assets/images/empty.svg';
|
import { ReactComponent as Emptyimage } from 'assets/images/empty.svg';
|
||||||
import NavLogo from 'common/components/nav/NavLogo';
|
|
||||||
import { formatDisplay } from 'common/utils/dateConfig';
|
import { formatDisplay } from 'common/utils/dateConfig';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
@@ -9,6 +8,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import Paginator from '../../../common/components/paginator/Paginator';
|
import Paginator from '../../../common/components/paginator/Paginator';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { formatTime } from '../../../common/utils/time';
|
import { formatTime } from '../../../common/utils/time';
|
||||||
@@ -69,7 +69,7 @@ export default function Pip(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`pip ${isMirrored ? 'mirror' : ''}`} data-testid='pip-view'>
|
<div className={`pip ${isMirrored ? 'mirror' : ''}`} data-testid='pip-view'>
|
||||||
<NavLogo />
|
<NavigationMenu />
|
||||||
|
|
||||||
<div className='event-title'>{general.title}</div>
|
<div className='event-title'>{general.title}</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import NavLogo from 'common/components/nav/NavLogo';
|
|
||||||
import TitleSide from 'common/components/title-side/TitleSide';
|
import TitleSide from 'common/components/title-side/TitleSide';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
@@ -8,6 +7,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import Paginator from '../../../common/components/paginator/Paginator';
|
import Paginator from '../../../common/components/paginator/Paginator';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { formatTime } from '../../../common/utils/time';
|
import { formatTime } from '../../../common/utils/time';
|
||||||
@@ -42,7 +42,7 @@ export default function Public(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
||||||
<NavLogo />
|
<NavigationMenu />
|
||||||
|
|
||||||
<div className='event-title'>{general.title}</div>
|
<div className='event-title'>{general.title}</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import useFitText from '../../../common/hooks/useFitText';
|
import useFitText from '../../../common/hooks/useFitText';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||||
@@ -58,7 +58,7 @@ export default function StudioClock(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
|
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
|
||||||
<NavLogo />
|
<NavigationMenu />
|
||||||
<div className='clock-container'>
|
<div className='clock-container'>
|
||||||
<div className='studio-timer'>{clock}</div>
|
<div className='studio-timer'>{clock}</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import TimerDisplay from 'common/components/countdown/TimerDisplay';
|
import TimerDisplay from 'common/components/countdown/TimerDisplay';
|
||||||
import MyProgressBar from 'common/components/myProgressBar/MyProgressBar';
|
import MyProgressBar from 'common/components/myProgressBar/MyProgressBar';
|
||||||
import NavLogo from 'common/components/nav/NavLogo';
|
|
||||||
import TitleCard from 'common/components/title-card/TitleCard';
|
import TitleCard from 'common/components/title-card/TitleCard';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
@@ -10,6 +9,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||||
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { formatTime } from '../../../common/utils/time';
|
import { formatTime } from '../../../common/utils/time';
|
||||||
|
|
||||||
@@ -72,12 +72,12 @@ export default function Timer(props) {
|
|||||||
return (
|
return (
|
||||||
<div className={time.finished ? `${baseClasses} stage-timer--finished` : baseClasses}
|
<div className={time.finished ? `${baseClasses} stage-timer--finished` : baseClasses}
|
||||||
data-testid='timer-view'>
|
data-testid='timer-view'>
|
||||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
<NavigationMenu />
|
||||||
|
<div
|
||||||
|
className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||||
<div className='message'>{pres.text}</div>
|
<div className='message'>{pres.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NavLogo />
|
|
||||||
|
|
||||||
<div className='clock-container'>
|
<div className='clock-container'>
|
||||||
<div className='label'>Time Now</div>
|
<div className='label'>Time Now</div>
|
||||||
<div className='clock'>{clock}</div>
|
<div className='clock'>{clock}</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const navigatorConstants = [
|
export const navigatorConstants = [
|
||||||
{ url: '/timer', label: 'Timer' },
|
{ url: '/timer', label: 'Timer' },
|
||||||
{ url: '/clock', label: 'Clock' },
|
{ url: '/clock', label: 'Clock' },
|
||||||
{ url: '/minimal', label: 'Minimal Timer' },
|
{ url: '/minimal', label: 'Minimal Timer' },
|
||||||
@@ -9,5 +9,3 @@ const navigatorConstants = [
|
|||||||
{ url: '/studio', label: 'Studio Clock' },
|
{ url: '/studio', label: 'Studio Clock' },
|
||||||
{ url: '/countdown', label: 'Countdown' },
|
{ url: '/countdown', label: 'Countdown' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default navigatorConstants;
|
|
||||||
@@ -12,39 +12,23 @@ import { devices } from '@playwright/test';
|
|||||||
*/
|
*/
|
||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
testDir: './tests',
|
testDir: './tests',
|
||||||
/* Maximum time one test can run for. */
|
timeout: 60 * 1000,
|
||||||
timeout: 30 * 1000,
|
|
||||||
expect: {
|
expect: {
|
||||||
/**
|
|
||||||
* Maximum time expect() should wait for the condition to be met.
|
|
||||||
* For example in `await expect(locator).toHaveText();`
|
|
||||||
*/
|
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
},
|
},
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
use: {
|
||||||
screenshot: 'only-on-failure', // Capture screenshot after each test failure
|
screenshot: 'only-on-failure',
|
||||||
viewport: { width: 1920, height: 1080 },
|
viewport: { width: 1920, height: 1080 },
|
||||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
actionTimeout: 5000,
|
||||||
actionTimeout: 0,
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: 'http://localhost:4001',
|
baseURL: 'http://localhost:4001',
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
@@ -55,7 +39,7 @@ const config: PlaywrightTestConfig = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: 'firefox',
|
name: 'firefox',
|
||||||
testMatch: /.*.spec.ts/,
|
testMatch: /.*.spec.all.ts/,
|
||||||
use: {
|
use: {
|
||||||
...devices['Desktop Firefox'],
|
...devices['Desktop Firefox'],
|
||||||
},
|
},
|
||||||
@@ -70,14 +54,13 @@ const config: PlaywrightTestConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
|
||||||
outputDir: 'test-results/',
|
outputDir: 'test-results/',
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'yarn start:server',
|
command: 'yarn start:server',
|
||||||
timeout: 120 * 1000,
|
timeout: 120 * 1000,
|
||||||
port: 4001,
|
port: 4001,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ test('test', async ({ context }) => {
|
|||||||
|
|
||||||
// stage timer message
|
// stage timer message
|
||||||
await editorPage.getByPlaceholder('Shown in stage timer').click();
|
await editorPage.getByPlaceholder('Shown in stage timer').click();
|
||||||
await editorPage.getByPlaceholder('Shown in stage timer').fill('testing');
|
await editorPage.getByPlaceholder('Shown in stage timer').fill('testing stage');
|
||||||
await editorPage.getByRole('button', { name: 'toggle timer screen message' }).click();
|
await editorPage.getByRole('button', { name: 'toggle timer screen message' }).click();
|
||||||
|
|
||||||
await featurePage.goto('http://localhost:4001/timer');
|
await featurePage.goto('http://localhost:4001/timer');
|
||||||
await featurePage.getByText('testing').click();
|
await featurePage.getByText('testing stage').click();
|
||||||
|
|
||||||
// public screen message
|
// public screen message
|
||||||
await editorPage.getByPlaceholder('Shown in public screens').click();
|
await editorPage.getByPlaceholder('Shown in public screens').click();
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('test view navigation feature', () => {
|
||||||
|
test('user flow through links', async ({ page }) => {
|
||||||
|
// default view is timer view
|
||||||
|
await page.goto('http://localhost:4001/');
|
||||||
|
await page.locator('data-test-id=timer-view');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Clock' }).click();
|
||||||
|
await page.locator('data-test-id=clock-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/clock');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Minimal Timer' }).click();
|
||||||
|
await page.locator('data-test-id=minimal-timer');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/minimal');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Backstage' }).click();
|
||||||
|
await page.locator('data-test-id=backstage-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/backstage');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Public' }).click();
|
||||||
|
await page.locator('data-test-id=public-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/public');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Lower Thirds' }).click();
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/lower');
|
||||||
|
const errorBoundary = await page.locator('data-test-id=error-container');
|
||||||
|
await expect(errorBoundary).toHaveCount(0);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'PiP' }).click();
|
||||||
|
await page.locator('data-test-id=pip-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/pip');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Studio Clock' }).click();
|
||||||
|
await page.locator('data-test-id=studio-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/studio');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Countdown' }).click();
|
||||||
|
await page.locator('data-test-id=countdown-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/countdown');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
|
await page.locator('data-test-id=navigation-menu');
|
||||||
|
await page.getByRole('link', { name: 'Timer' }).click();
|
||||||
|
await page.locator('data-test-id=timer-view');
|
||||||
|
await expect(page).toHaveURL('http://localhost:4001/timer');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -39,40 +39,4 @@ test.describe('pages routes are available', () => {
|
|||||||
await page.getByTestId('panel-info').click();
|
await page.getByTestId('panel-info').click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('client routes', () => {
|
|
||||||
test('stage timer', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/timer');
|
|
||||||
await page.getByTestId('timer-view').click();
|
|
||||||
});
|
|
||||||
test('clock', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/clock');
|
|
||||||
await page.getByTestId('clock-view').click();
|
|
||||||
});
|
|
||||||
test('minimal timer', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/minimal');
|
|
||||||
await page.getByTestId('minimal-timer').click();
|
|
||||||
});
|
|
||||||
test('backstage', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/backstage');
|
|
||||||
await page.getByTestId('backstage-view').click();
|
|
||||||
});
|
|
||||||
test('public', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/public');
|
|
||||||
await page.getByTestId('public-view').click();
|
|
||||||
});
|
|
||||||
test('pip', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/pip');
|
|
||||||
await page.getByTestId('pip-view').click();
|
|
||||||
});
|
|
||||||
test('studio clock', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/studio');
|
|
||||||
await page.getByTestId('studio-view').click();
|
|
||||||
});
|
|
||||||
test('lower', async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:4001/lower');
|
|
||||||
const errorBoundary = await page.locator('data-test-id=error-container');
|
|
||||||
await expect(errorBoundary).toHaveCount(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user