diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index efa0ef2ba9d..f7b27a767f4 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -24,6 +24,7 @@ _Released 11/4/2025 (PENDING)_ - Have cursor on hover of the AUT URL to show as pointer. Addresses [#32777](https://github.com/cypress-io/cypress/issues/32777). Addressed in [#32782](https://github.com/cypress-io/cypress/pull/32782). - WebKit now prefers a cookie's fully qualified `domain` when requesting a cookie value via [`cy.getCookie()`](https://docs.cypress.io/api/commands/getcookie). If none are found, the cookie's apex domain will be used as a fallback. Addresses [#29954](https://github.com/cypress-io/cypress/issues/29954), [#29973](https://github.com/cypress-io/cypress/issues/29973) and [#30392](https://github.com/cypress-io/cypress/issues/30392). Addressed in [#32852](https://github.com/cypress-io/cypress/pull/32852). - The 'Next' tooltip style was updated. Addressed in [#32866](https://github.com/cypress-io/cypress/pull/32866). +- Make test name header sticky in studio mode and in the tests list. Addresses [#32591](https://github.com/cypress-io/cypress/issues/32591). Addressed in [#32840](https://github.com/cypress-io/cypress/pull/32840) **Dependency Updates:** diff --git a/packages/reporter/src/collapsible/collapsible.scss b/packages/reporter/src/collapsible/collapsible.scss index b423ca0835c..e9a11920788 100644 --- a/packages/reporter/src/collapsible/collapsible.scss +++ b/packages/reporter/src/collapsible/collapsible.scss @@ -1,3 +1,5 @@ +@import "../lib/mixins.scss"; + .reporter { .collapsible-indicator { margin-right: 8px; @@ -8,4 +10,8 @@ .is-open > .collapsible-header-wrapper > .collapsible-header > .collapsible-header-inner > .collapsible-indicator { transform: rotate(0); } + + .test > .is-open > .collapsible-header-wrapper { + @include sticky-header-with-shadow; + } } diff --git a/packages/reporter/src/collapsible/collapsible.tsx b/packages/reporter/src/collapsible/collapsible.tsx index 287a5b768da..fe3a743963f 100644 --- a/packages/reporter/src/collapsible/collapsible.tsx +++ b/packages/reporter/src/collapsible/collapsible.tsx @@ -1,5 +1,5 @@ import cs from 'classnames' -import React, { CSSProperties, MouseEvent, ReactNode, RefObject, useCallback, useState } from 'react' +import React, { CSSProperties, MouseEvent, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react' import { onEnterOrSpace } from '../lib/util' import DocumentBlankIcon from '@packages/frontend-shared/src/assets/icons/document-blank_x16.svg' import { IconChevronDownSmall } from '@cypress-design/react-icon' @@ -24,6 +24,8 @@ interface CollapsibleProps { const Collapsible: React.FC = ({ isOpen: isOpenAsProp = false, header, headerClass = '', headerStyle = {}, headerExtras, contentClass = '', hideExpander = false, containerRef = null, onOpenStateChangeRequested, children, HeaderComponent }) => { const [isOpenState, setIsOpenState] = useState(isOpenAsProp) + const headerRef = useRef(null) + const fixedElementRef = useRef(null) const toggleOpenState = useCallback((e?: MouseEvent) => { e?.stopPropagation() @@ -36,9 +38,27 @@ const Collapsible: React.FC = ({ isOpen: isOpenAsProp = false, const isOpen = onOpenStateChangeRequested ? isOpenAsProp : isOpenState + const toggleHeaderShadow = (entries) => { + const [entry] = entries + + headerRef.current?.classList.toggle('shadow-active', !entry.isIntersecting) + } + + useEffect(() => { + if (!fixedElementRef?.current) return + + const observer = new IntersectionObserver(toggleHeaderShadow) + + observer.observe(fixedElementRef.current) + + return () => observer.disconnect() + }, []) + return (
-
+ {/* This empty div acts as an intersection observer target to toggle the header shadow based on scroll position */} +
+
(null) + const testSectionRef = useRef(null) + const fixedElementRef = useRef(null) const { containerRef, isMounted, scrollIntoView } = useScrollIntoView({ appState, @@ -89,10 +91,28 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St const testTitle = currentTest ? {currentTest.title} : null + const toggleHeaderShadow = (entries) => { + const [entry] = entries + + testSectionRef.current?.classList.toggle('shadow-active', !entry.isIntersecting) + } + + useEffect(() => { + if (!fixedElementRef.current) return + + const observer = new IntersectionObserver(toggleHeaderShadow) + + observer.observe(fixedElementRef.current) + + return () => observer.disconnect() + }, []) + return ( - currentTest && ( -
-
+ currentTest && (<> + {/* This empty div acts as an intersection observer target to toggle the header shadow based on scroll position */} +
+
+
All tests

} className='cy-tooltip'> @@ -126,6 +146,7 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St
+ ) ) })