diff --git a/client/src/common/components/nav/NavLogo.jsx b/client/src/common/components/nav/NavLogo.jsx deleted file mode 100644 index 414b4c9f8..000000000 --- a/client/src/common/components/nav/NavLogo.jsx +++ /dev/null @@ -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 ( - - - - {showNav && ( - <> - - } - onClick={() => setMirrored((prev) => !prev)} - {...navButtonStyle} - /> - : } - onClick={toggleFullScreen} - {...navButtonStyle} - /> - - - {navigatorConstants.map((route) => ( - - {route.label} - - ))} - - - )} - - - ); -} - -NavLogo.propTypes = { - isHidden: PropTypes.bool, -}; diff --git a/client/src/common/components/nav/NavLogo.module.scss b/client/src/common/components/nav/NavLogo.module.scss deleted file mode 100644 index 680a1dff0..000000000 --- a/client/src/common/components/nav/NavLogo.module.scss +++ /dev/null @@ -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; - } -} diff --git a/client/src/common/components/navigation-menu/NavigationMenu.module.scss b/client/src/common/components/navigation-menu/NavigationMenu.module.scss new file mode 100644 index 000000000..d14cde0a8 --- /dev/null +++ b/client/src/common/components/navigation-menu/NavigationMenu.module.scss @@ -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; +} diff --git a/client/src/common/components/navigation-menu/NavigationMenu.tsx b/client/src/common/components/navigation-menu/NavigationMenu.tsx new file mode 100644 index 000000000..1b7d33715 --- /dev/null +++ b/client/src/common/components/navigation-menu/NavigationMenu.tsx @@ -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) => event.key === 'Enter'; + const handleFullscreen = () => toggleFullScreen(); + const handleMirror = () => setMirrored((prev) => !prev); + + return createPortal( + , document.body); +} diff --git a/client/src/common/hooks/useKeyDown.ts b/client/src/common/hooks/useKeyDown.ts new file mode 100644 index 000000000..4fbee6d36 --- /dev/null +++ b/client/src/common/hooks/useKeyDown.ts @@ -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]); +}; diff --git a/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap b/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap new file mode 100644 index 000000000..790bd1708 --- /dev/null +++ b/client/src/common/utils/__tests__/__snapshots__/styleUtils.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1 + +exports[`cx() > ignores falsy values 1`] = `""`; + +exports[`cx() > merges styles 1`] = `"_test_98a1e0 _another_98a1e0"`; diff --git a/client/src/common/utils/__tests__/styleUtils.module.scss b/client/src/common/utils/__tests__/styleUtils.module.scss new file mode 100644 index 000000000..f15ae3fd8 --- /dev/null +++ b/client/src/common/utils/__tests__/styleUtils.module.scss @@ -0,0 +1,2 @@ +.test {} +.another {} diff --git a/client/src/common/utils/__tests__/styleUtils.test.ts b/client/src/common/utils/__tests__/styleUtils.test.ts new file mode 100644 index 000000000..a8db697da --- /dev/null +++ b/client/src/common/utils/__tests__/styleUtils.test.ts @@ -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(); + }); +}); diff --git a/client/src/common/utils/styleUtils.ts b/client/src/common/utils/styleUtils.ts index d9c94b265..2be4ff166 100644 --- a/client/src/common/utils/styleUtils.ts +++ b/client/src/common/utils/styleUtils.ts @@ -26,4 +26,4 @@ export const getAccessibleColour = (bgColour: string): ColourCombination => { * @description Creates a list of classnames from array of css module conditions * @param classNames - css modules objects */ -export const cx = (...classNames: any[]) => classNames.filter(Boolean).join(" ") +export const cx = (classNames: any[]) => classNames.filter(Boolean).join(" "); diff --git a/client/src/features/viewers/backstage/Backstage.jsx b/client/src/features/viewers/backstage/Backstage.jsx index fd05b0634..661bd05e2 100644 --- a/client/src/features/viewers/backstage/Backstage.jsx +++ b/client/src/features/viewers/backstage/Backstage.jsx @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; import QRCode from 'react-qr-code'; -import NavLogo from 'common/components/nav/NavLogo'; import TitleSide from 'common/components/title-side/TitleSide'; import { formatDisplay } from 'common/utils/dateConfig'; import { AnimatePresence, motion } from 'framer-motion'; @@ -9,6 +8,7 @@ import PropTypes from 'prop-types'; import { overrideStylesURL } from '../../../common/api/apiConstants'; import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings'; +import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import Paginator from '../../../common/components/paginator/Paginator'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { getEventsWithDelay } from '../../../common/utils/eventsManager'; @@ -62,7 +62,7 @@ export default function Backstage(props) { return (
- +
{general.title}
diff --git a/client/src/features/viewers/clock/Clock.tsx b/client/src/features/viewers/clock/Clock.tsx index 99e520d1f..df99547b3 100644 --- a/client/src/features/viewers/clock/Clock.tsx +++ b/client/src/features/viewers/clock/Clock.tsx @@ -4,7 +4,7 @@ import { useAtom } from 'jotai'; import { overrideStylesURL } from '../../../common/api/apiConstants'; 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 { TimeManagerType } from '../../../common/models/TimeManaget.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 clean = clock.replace('/:/g', ''); @@ -137,7 +134,7 @@ export default function Clock(props: ClockProps) { }} data-testid='clock-view' > - {!userOptions?.hideNav && } +
- + {follow === null ? (
Select an event to follow diff --git a/client/src/features/viewers/lower-thirds/LowerClean.jsx b/client/src/features/viewers/lower-thirds/LowerClean.jsx index b4ed06c2e..ca0ff8bc6 100644 --- a/client/src/features/viewers/lower-thirds/LowerClean.jsx +++ b/client/src/features/viewers/lower-thirds/LowerClean.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import NavLogo from "../../../common/components/nav/NavLogo"; +import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import './LowerClean.scss'; @@ -87,7 +87,7 @@ export default function LowerClean(props) { }} > - + {showLower && ( diff --git a/client/src/features/viewers/lower-thirds/LowerLines.jsx b/client/src/features/viewers/lower-thirds/LowerLines.jsx index 0923cfce8..bcf39dbfe 100644 --- a/client/src/features/viewers/lower-thirds/LowerLines.jsx +++ b/client/src/features/viewers/lower-thirds/LowerLines.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import NavLogo from "../../../common/components/nav/NavLogo"; +import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import './LowerLines.scss'; @@ -130,7 +130,7 @@ export default function LowerLines(props) { }} > - + {showLower && ( diff --git a/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index 5fe407161..7dce25e73 100644 --- a/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -4,7 +4,7 @@ import { useAtom } from 'jotai'; import { overrideStylesURL } from '../../../common/api/apiConstants'; 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 { PresenterMessageType } from '../../../common/models/PresenterMessage.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'); userOptions.hideOvertime = Boolean(hideOvertime); @@ -144,6 +141,7 @@ export default function MinimalTimer(props: MinimalTimerProps) { }} data-testid='minimal-timer' > + {!hideMessagesOverlay && (
{pres.text}
)} - {!userOptions?.hideNav && }
- +
{general.title}
diff --git a/client/src/features/viewers/public/Public.jsx b/client/src/features/viewers/public/Public.jsx index 4b02f999c..76ee787b3 100644 --- a/client/src/features/viewers/public/Public.jsx +++ b/client/src/features/viewers/public/Public.jsx @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; import QRCode from 'react-qr-code'; -import NavLogo from 'common/components/nav/NavLogo'; import TitleSide from 'common/components/title-side/TitleSide'; import { AnimatePresence, motion } from 'framer-motion'; import { useAtom } from 'jotai'; @@ -8,6 +7,7 @@ import PropTypes from 'prop-types'; import { overrideStylesURL } from '../../../common/api/apiConstants'; import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings'; +import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import Paginator from '../../../common/components/paginator/Paginator'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { formatTime } from '../../../common/utils/time'; @@ -42,7 +42,7 @@ export default function Public(props) { return (
- +
{general.title}
diff --git a/client/src/features/viewers/studio/StudioClock.jsx b/client/src/features/viewers/studio/StudioClock.jsx index 2027e6176..5f1d708d3 100644 --- a/client/src/features/viewers/studio/StudioClock.jsx +++ b/client/src/features/viewers/studio/StudioClock.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { overrideStylesURL } from '../../../common/api/apiConstants'; 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 { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { formatDisplay } from '../../../common/utils/dateConfig'; @@ -58,7 +58,7 @@ export default function StudioClock(props) { return (
- +
{clock}
-
+ +
{pres.text}
- -
Time Now
{clock}
diff --git a/client/src/common/components/nav/navigatorConstants.js b/client/src/viewerConfig.ts similarity index 85% rename from client/src/common/components/nav/navigatorConstants.js rename to client/src/viewerConfig.ts index b9d37b44e..c2ad4beeb 100644 --- a/client/src/common/components/nav/navigatorConstants.js +++ b/client/src/viewerConfig.ts @@ -1,4 +1,4 @@ -const navigatorConstants = [ +export const navigatorConstants = [ { url: '/timer', label: 'Timer' }, { url: '/clock', label: 'Clock' }, { url: '/minimal', label: 'Minimal Timer' }, @@ -9,5 +9,3 @@ const navigatorConstants = [ { url: '/studio', label: 'Studio Clock' }, { url: '/countdown', label: 'Countdown' }, ]; - -export default navigatorConstants; diff --git a/server/playwright.config.ts b/server/playwright.config.ts index 80a3b73b1..470c5c6de 100644 --- a/server/playwright.config.ts +++ b/server/playwright.config.ts @@ -12,39 +12,23 @@ import { devices } from '@playwright/test'; */ const config: PlaywrightTestConfig = { testDir: './tests', - /* Maximum time one test can run for. */ - timeout: 30 * 1000, + timeout: 60 * 1000, expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ timeout: 5000 }, - /* Run tests in files in parallel */ fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - screenshot: 'only-on-failure', // Capture screenshot after each test failure + screenshot: 'only-on-failure', viewport: { width: 1920, height: 1080 }, - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ + actionTimeout: 5000, baseURL: 'http://localhost:4001', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', }, - /* Configure projects for major browsers */ projects: [ { name: 'chromium', @@ -55,7 +39,7 @@ const config: PlaywrightTestConfig = { { name: 'firefox', - testMatch: /.*.spec.ts/, + testMatch: /.*.spec.all.ts/, use: { ...devices['Desktop Firefox'], }, @@ -70,14 +54,13 @@ const config: PlaywrightTestConfig = { }, ], - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ outputDir: 'test-results/', - /* Run your local dev server before starting the tests */ webServer: { command: 'yarn start:server', timeout: 120 * 1000, port: 4001, + reuseExistingServer: !process.env.CI, }, }; diff --git a/server/tests/features/message-control.spec.ts b/server/tests/features/message-control.spec.ts index 7e5d55b0b..76a9a0e52 100644 --- a/server/tests/features/message-control.spec.ts +++ b/server/tests/features/message-control.spec.ts @@ -8,11 +8,11 @@ test('test', async ({ context }) => { // stage timer message 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 featurePage.goto('http://localhost:4001/timer'); - await featurePage.getByText('testing').click(); + await featurePage.getByText('testing stage').click(); // public screen message await editorPage.getByPlaceholder('Shown in public screens').click(); diff --git a/server/tests/features/view-navigation.test.ts b/server/tests/features/view-navigation.test.ts new file mode 100644 index 000000000..124d57a77 --- /dev/null +++ b/server/tests/features/view-navigation.test.ts @@ -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'); + }); +}); diff --git a/server/tests/smoke-tests.test.ts b/server/tests/smoke-tests.test.ts index c0580c260..ed3f39e56 100644 --- a/server/tests/smoke-tests.test.ts +++ b/server/tests/smoke-tests.test.ts @@ -39,40 +39,4 @@ test.describe('pages routes are available', () => { 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); - }); - }); });