mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-04 06:10:30 -06:00
misc: Add more spec actions button and new back button for Studio mode (#32611)
* chore: Add more spec actions button * Update styles, and update studio components * Refactor Reporter components to streamline studio test handling - Removed `displayStatsAndControls` prop from `Header` and adjusted rendering logic based on `isStudioSingleTest`. - Updated `RunnableHeader` and `RunnablePopoverOptions` to conditionally render elements based on `isStudioSingleTest`. - Improved code readability and maintainability by consolidating logic related to studio tests. * Refactor Header and StudioTest components for improved styling and functionality - Updated the `Header` component to remove the `isStudioSingleTest` prop from `RunnableHeader` rendering. - Modified the `StudioTest` component to change the button styling from 'outline-dark' to 'outline-indigo' and updated the stroke color of the icon accordingly. * Update tests * Update tests * Fix styles * changelog entry * Add all tests tooltip * Update styles * maybe make semantic-commit happy --------- Co-authored-by: Jennifer Shehane <jennifer@cypress.io> Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ac0ad316a6
commit
3909ed07a0
@@ -15,6 +15,8 @@ _Released 10/07/2025 (PENDING)_
|
||||
|
||||
**Misc:**
|
||||
|
||||
- Added a dropdown menu in the Command Log that includes actions like Open in IDE and Add New Test in Studio, along with test preferences such as Auto-Scroll. Addresses [#32556](https://github.com/cypress-io/cypress/issues/32556) and [#32558](https://github.com/cypress-io/cypress/issues/32558). Addressed in [#32611](https://github.com/cypress-io/cypress/pull/32611).
|
||||
- Updated the Studio test editing header to include a Back button. This change ensures the Specs button remains functional for expanding or collapsing the specs panel. Addresses [#32556](https://github.com/cypress-io/cypress/issues/32556) and [#32558](https://github.com/cypress-io/cypress/issues/32558). Addressed in [#32611](https://github.com/cypress-io/cypress/pull/32611).
|
||||
- Fixed the Studio panel resizing when dragging. Addressed in [#32584](https://github.com/cypress-io/cypress/pull/32584).
|
||||
|
||||
## 15.3.0
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('Reporter Header', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('Testing Preferences', () => {
|
||||
context('More actions button', () => {
|
||||
const switchSelector = '[data-cy=auto-scroll-switch]'
|
||||
|
||||
context('preferences menu', () => {
|
||||
@@ -54,19 +54,23 @@ describe('Reporter Header', () => {
|
||||
cy.waitForSpecToFinish()
|
||||
})
|
||||
|
||||
it('clicking the down arrow will open a panel showing Testing Preferences', () => {
|
||||
cy.get('[data-cy=testing-preferences-toggle]').trigger('mouseover')
|
||||
cy.get('.cy-tooltip').should('have.text', 'Open Testing Preferences')
|
||||
it('clicking down more options will open a popover with more options', () => {
|
||||
cy.get('[data-cy="runnable-options-button"]').trigger('mouseover')
|
||||
cy.get('.cy-tooltip').should('have.text', 'Options')
|
||||
|
||||
cy.get('.testing-preferences').should('not.exist')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('.testing-preferences').should('be.visible')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('.testing-preferences').should('not.exist')
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('not.exist')
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('not.exist')
|
||||
})
|
||||
|
||||
it('will show a toggle beside the auto-scrolling option', () => {
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
it('will show multiples actions in the popover', () => {
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('contain', 'Open in IDE')
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('contain', 'New test')
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('contain', 'Auto-scrolling')
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'true')
|
||||
cy.get(switchSelector).click()
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'false')
|
||||
@@ -95,7 +99,9 @@ describe('Reporter Header', () => {
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -171,9 +171,9 @@ describe('src/cypress/runner', () => {
|
||||
o.sinon.stub(ctx.actions.file, 'openFile')
|
||||
})
|
||||
|
||||
cy.get('.open-in-ide-button').should('have.css', 'opacity', '0')
|
||||
cy.get('.spec-file-name').realHover()
|
||||
cy.get('.open-in-ide-button').first().should('have.css', 'opacity', '1').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy="runnable-popover-open-ide"]').click()
|
||||
|
||||
cy.withCtx((ctx, o) => {
|
||||
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`simple-cy-assert\.runner\.cy\.js$`)), 1, 1)
|
||||
|
||||
@@ -30,7 +30,9 @@ export function launchStudio ({ specName = 'spec.cy.js', createNewTestFromSuite
|
||||
|
||||
if (createNewTestFromSuite || createNewTestFromSpecHeader) {
|
||||
if (createNewTestFromSpecHeader) {
|
||||
cy.findByTestId('create-new-test-from-spec-header').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy="runnable-popover-new-test"]').click()
|
||||
} else {
|
||||
cy.get('@runnable-wrapper').realHover().findByTestId('create-new-test-from-suite').click()
|
||||
}
|
||||
|
||||
@@ -623,10 +623,13 @@ describe('studio functionality', () => {
|
||||
o.sinon.stub(ctx.actions.file, 'openFile')
|
||||
})
|
||||
|
||||
cy.get('.open-in-ide-button').should('have.css', 'opacity', '0')
|
||||
cy.get('.spec-file-name').first().realHover()
|
||||
cy.get('.open-in-ide-button').first().should('have.css', 'opacity', '1').click()
|
||||
cy.get('.open-in-ide-button').first().contains('Open in IDE')
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get('[data-cy="runnable-popover-open-ide"]').contains('Open in IDE')
|
||||
cy.get('[data-cy="runnable-popover-open-ide"]').click()
|
||||
|
||||
cy.contains('External editor preferences')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
@@ -103,24 +103,28 @@ describe('header', () => {
|
||||
runner.emit('run:start')
|
||||
})
|
||||
|
||||
describe('preferences menu', () => {
|
||||
it('can be toggled', () => {
|
||||
cy.get('.testing-preferences').should('not.exist')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('.testing-preferences').should('be.visible')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('.testing-preferences').should('not.exist')
|
||||
describe('more options menu', () => {
|
||||
it('can be opened', () => {
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('not.exist')
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
})
|
||||
|
||||
it('has tooltip', () => {
|
||||
cy.get('[data-cy=testing-preferences-toggle]').trigger('mouseover')
|
||||
cy.get('.cy-tooltip').should('have.text', 'Open Testing Preferences')
|
||||
cy.get('[data-cy="runnable-options-button"]').trigger('mouseover')
|
||||
cy.get('.cy-tooltip').should('have.text', 'Options')
|
||||
})
|
||||
|
||||
it('shows when auto-scrolling is enabled and can disable it', () => {
|
||||
const switchSelector = '[data-cy=auto-scroll-switch]'
|
||||
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'true')
|
||||
cy.get(switchSelector).click()
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'false')
|
||||
@@ -129,7 +133,9 @@ describe('header', () => {
|
||||
it('can be toggled with shortcut', () => {
|
||||
const switchSelector = '[data-cy=auto-scroll-switch]'
|
||||
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'true')
|
||||
cy.get('body').type('a').then(() => {
|
||||
cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'false')
|
||||
@@ -138,11 +144,34 @@ describe('header', () => {
|
||||
|
||||
it('the auto-scroll toggle emits save:state event when clicked', () => {
|
||||
cy.spy(runner, 'emit')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get('[data-cy=auto-scroll-switch]').click()
|
||||
cy.wrap(runner.emit).should('be.calledWith', 'save:state')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('opens the open in IDE button', () => {
|
||||
cy.spy(runner, 'emit')
|
||||
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get('[data-cy="runnable-popover-open-ide"]').click()
|
||||
cy.wrap(runner.emit).should('be.calledWith', 'open:file:unified')
|
||||
})
|
||||
|
||||
it('opens the new test button', () => {
|
||||
cy.spy(runner, 'emit')
|
||||
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
|
||||
cy.get('[data-cy="runnable-popover-new-test"]').click()
|
||||
cy.wrap(runner.emit).should('be.calledWith', 'studio:init:suite')
|
||||
})
|
||||
})
|
||||
|
||||
describe('stop button', () => {
|
||||
|
||||
@@ -132,7 +132,7 @@ describe('runnables', () => {
|
||||
runner.emit('reporter:start', { startTime: startTime.toISOString() })
|
||||
})
|
||||
|
||||
cy.get('.runnable-header span:last').should('have.text', '00:12')
|
||||
cy.get('.runnable-header .duration').should('have.text', '00:12')
|
||||
})
|
||||
|
||||
it('does not display time if no time taken', () => {
|
||||
@@ -208,8 +208,9 @@ describe('runnables', () => {
|
||||
|
||||
cy.stub(runner, 'emit').callThrough()
|
||||
|
||||
cy.get(selector).as('spec-title').contains('foo.js').realHover()
|
||||
cy.get('.open-in-ide-button').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy="runnable-popover-open-ide"]').click()
|
||||
cy.get(selector).click().then(() => {
|
||||
expect(runner.emit).to.be.calledWith('open:file:unified')
|
||||
})
|
||||
|
||||
@@ -118,11 +118,15 @@ describe('shortcuts', function () {
|
||||
|
||||
it('toggles auto-scrolling', () => {
|
||||
cy.get('body').type('a')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy=auto-scroll-switch]').invoke('attr', 'aria-checked').should('eq', 'false')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('[data-cy=runnable-options-button]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('not.exist')
|
||||
|
||||
cy.get('body').type('a')
|
||||
cy.get('[data-cy=testing-preferences-toggle]').click()
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy=auto-scroll-switch]').invoke('attr', 'aria-checked').should('eq', 'true')
|
||||
})
|
||||
|
||||
|
||||
@@ -60,19 +60,18 @@ describe('spec title', () => {
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('displays Open in IDE button on spec name hover', () => {
|
||||
cy.get('.open-in-ide-button').should('have.css', 'opacity', '0')
|
||||
|
||||
cy.get('.spec-file-name').realHover()
|
||||
cy.get('.open-in-ide-button').should('have.css', 'opacity', '1')
|
||||
cy.get('.open-in-ide-button').contains('Open in IDE')
|
||||
it('displays Open in IDE button on more actions button', () => {
|
||||
cy.get('[data-cy="runnable-options-button"]').click()
|
||||
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
|
||||
cy.get('[data-cy="runnable-popover-open-ide"]').contains('Open in IDE')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
itHandlesFileOpening({
|
||||
getRunner: () => runner,
|
||||
selector: '.open-in-ide-button',
|
||||
previousClickSelector: '[data-cy="runnable-options-button"]',
|
||||
selector: '[data-cy="runnable-popover-open-ide"]',
|
||||
file: {
|
||||
file: '/absolute/path/to/foo.js',
|
||||
line: 0,
|
||||
|
||||
@@ -141,25 +141,6 @@ describe('app state', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('#togglePreferencesMenu', () => {
|
||||
it('toggles isPreferencesMenuOpen', () => {
|
||||
const instance = new AppState()
|
||||
|
||||
instance.togglePreferencesMenu()
|
||||
expect(instance.isPreferencesMenuOpen).to.be.true
|
||||
instance.togglePreferencesMenu()
|
||||
expect(instance.isPreferencesMenuOpen).to.be.false
|
||||
})
|
||||
|
||||
it('sets reset value for autoScrollingEnabled', () => {
|
||||
const instance = new AppState()
|
||||
|
||||
instance.togglePreferencesMenu()
|
||||
instance.reset()
|
||||
expect(instance.autoScrollingEnabled).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
context('#setStudioActive', () => {
|
||||
it('sets studioActive', () => {
|
||||
const instance = new AppState()
|
||||
|
||||
@@ -14,9 +14,10 @@ interface HandlesFileOpeningProps {
|
||||
selector: string
|
||||
file: File
|
||||
stackTrace?: boolean
|
||||
previousClickSelector?: string
|
||||
}
|
||||
|
||||
export const itHandlesFileOpening = ({ getRunner, selector, file, stackTrace = false }: HandlesFileOpeningProps) => {
|
||||
export const itHandlesFileOpening = ({ getRunner, selector, file, stackTrace = false, previousClickSelector }: HandlesFileOpeningProps) => {
|
||||
describe('it handles file opening', () => {
|
||||
it('emits unified file open event', () => {
|
||||
cy.stub(getRunner(), 'emit').callThrough()
|
||||
@@ -25,6 +26,10 @@ export const itHandlesFileOpening = ({ getRunner, selector, file, stackTrace = f
|
||||
cy.contains('Stack trace').click()
|
||||
}
|
||||
|
||||
if (previousClickSelector) {
|
||||
cy.get(previousClickSelector).click()
|
||||
}
|
||||
|
||||
cy.get(selector).first().click().then(() => {
|
||||
expect(getRunner().emit).to.be.calledWith('open:file:unified')
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { action } from 'mobx'
|
||||
import { observer } from 'mobx-react'
|
||||
import React from 'react'
|
||||
import Button from '@cypress-design/react-button'
|
||||
@@ -8,7 +7,7 @@ import Tooltip from '@cypress/react-tooltip'
|
||||
import defaultEvents, { Events } from '../lib/events'
|
||||
import type { AppState } from '../lib/app-state'
|
||||
|
||||
import { IconChevronDownSmall, IconChevronUpSmall, IconActionNext, IconActionPlayLarge, IconActionRestart, IconActionStopCircle } from '@cypress-design/react-icon'
|
||||
import { IconActionNext, IconActionPlayLarge, IconActionRestart, IconActionStopCircle } from '@cypress-design/react-icon'
|
||||
|
||||
const iconStrokeColor = 'gray-500'
|
||||
const iconFillColor = 'gray-900'
|
||||
@@ -16,37 +15,13 @@ const iconFillColor = 'gray-900'
|
||||
interface Props {
|
||||
events?: Events
|
||||
appState: AppState
|
||||
displayPreferencesButton?: boolean
|
||||
}
|
||||
|
||||
const Controls: React.FC<Props> = observer(({ events = defaultEvents, appState, displayPreferencesButton = true }: Props) => {
|
||||
const Controls: React.FC<Props> = observer(({ events = defaultEvents, appState }: Props) => {
|
||||
const emit = (event: string) => () => events.emit(event)
|
||||
const togglePreferencesMenu = () => {
|
||||
appState.togglePreferencesMenu()
|
||||
events.emit('save:state')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='controls'>
|
||||
{displayPreferencesButton && (
|
||||
<Tooltip placement='bottom' title={<p>Open Testing Preferences</p>} className='cy-tooltip'>
|
||||
<div>
|
||||
<Button
|
||||
size='20'
|
||||
variant='outline-dark'
|
||||
aria-label='Open testing preferences'
|
||||
data-cy='testing-preferences-toggle'
|
||||
onClick={action('toggle:preferences:menu', togglePreferencesMenu)}
|
||||
>
|
||||
{appState.isPreferencesMenuOpen ? (
|
||||
<IconChevronUpSmall strokeColor='gray-500' />
|
||||
) : (
|
||||
<IconChevronDownSmall strokeColor='gray-500' />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{appState.isPaused && (
|
||||
<Tooltip placement='bottom' title={<p>Resume <span className='kbd'>C</span></p>} className='cy-tooltip'>
|
||||
<div>
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface ReporterHeaderProps {
|
||||
}
|
||||
|
||||
const Header: React.FC<ReporterHeaderProps> = observer(({ appState, events = defaultEvents, statsStore, runnablesStore, spec }: ReporterHeaderProps) => {
|
||||
const isStudioSingleTest = appState?.studioActive && appState.studioSingleTestActive
|
||||
|
||||
return <header>
|
||||
<div className='spec-container'>
|
||||
<Tooltip placement='bottom' title={<p>{appState.isSpecsListOpen ? 'Collapse' : 'Expand'} Specs List <span className='kbd'>F</span></p>} wrapperClassName='toggle-specs-wrapper' className='cy-tooltip'>
|
||||
@@ -46,10 +48,10 @@ const Header: React.FC<ReporterHeaderProps> = observer(({ appState, events = def
|
||||
</Tooltip>
|
||||
{spec && <RunnableHeader spec={spec} statsStore={statsStore} runnablesStore={runnablesStore} />}
|
||||
</div>
|
||||
<div className='statsAndControls'>
|
||||
{!isStudioSingleTest && <div className='statsAndControls'>
|
||||
<Stats stats={statsStore} />
|
||||
<Controls appState={appState} />
|
||||
</div>
|
||||
</div>}
|
||||
</header>
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { observable, makeObservable } from 'mobx'
|
||||
interface DefaultAppState {
|
||||
isPaused: boolean
|
||||
isRunning: boolean
|
||||
isPreferencesMenuOpen: boolean
|
||||
nextCommandName: string | null | undefined
|
||||
pinnedSnapshotId: number | string | null
|
||||
studioActive: boolean
|
||||
@@ -16,7 +15,6 @@ interface DefaultAppState {
|
||||
const defaults: DefaultAppState = {
|
||||
isPaused: false,
|
||||
isRunning: false,
|
||||
isPreferencesMenuOpen: false,
|
||||
nextCommandName: null,
|
||||
pinnedSnapshotId: null,
|
||||
studioActive: false,
|
||||
@@ -29,7 +27,6 @@ class AppState {
|
||||
isSpecsListOpen = false
|
||||
isPaused = defaults.isPaused
|
||||
isRunning = defaults.isRunning
|
||||
isPreferencesMenuOpen = defaults.isPreferencesMenuOpen
|
||||
nextCommandName = defaults.nextCommandName
|
||||
pinnedSnapshotId = defaults.pinnedSnapshotId
|
||||
studioActive = defaults.studioActive
|
||||
@@ -45,7 +42,6 @@ class AppState {
|
||||
isSpecsListOpen: observable,
|
||||
isPaused: observable,
|
||||
isRunning: observable,
|
||||
isPreferencesMenuOpen: observable,
|
||||
nextCommandName: observable,
|
||||
pinnedSnapshotId: observable,
|
||||
studioActive: observable,
|
||||
@@ -99,10 +95,6 @@ class AppState {
|
||||
this.isSpecsListOpen = !this.isSpecsListOpen
|
||||
}
|
||||
|
||||
togglePreferencesMenu () {
|
||||
this.isPreferencesMenuOpen = !this.isPreferencesMenuOpen
|
||||
}
|
||||
|
||||
setSpecsList (status: boolean) {
|
||||
this.isSpecsListOpen = status
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ import shortcuts from './lib/shortcuts'
|
||||
|
||||
import Header, { ReporterHeaderProps } from './header/header'
|
||||
import Runnables from './runnables/runnables'
|
||||
import TestingPreferences from './preferences/testing-preferences'
|
||||
import type { MobxRunnerStore } from '@packages/app/src/store/mobx-runner-store'
|
||||
import { StudioTestHeader } from './studio/StudioTestHeader'
|
||||
|
||||
function usePrevious (value) {
|
||||
const ref = useRef()
|
||||
@@ -105,18 +103,12 @@ const Reporter: React.FC<SingleReporterProps> = observer(({ appState = appStateD
|
||||
}
|
||||
}, [runnerStore.spec, runnerStore.specRunId, resetStatsOnSpecChange, previousSpecRunId])
|
||||
|
||||
const isStudioSingleTest = appState?.studioActive && appState.studioSingleTestActive
|
||||
|
||||
return (
|
||||
<div className={cs(className, 'reporter', {
|
||||
'mounted': isMounted,
|
||||
})}>
|
||||
{isStudioSingleTest && runnerStore.spec ? <StudioTestHeader
|
||||
spec={runnerStore.spec}
|
||||
/> : renderReporterHeader({ appState, statsStore, runnablesStore, spec: runnerStore.spec })}
|
||||
{appState?.isPreferencesMenuOpen ? (
|
||||
<TestingPreferences appState={appState} />
|
||||
) : (
|
||||
{renderReporterHeader({ appState, statsStore, runnablesStore, spec: runnerStore.spec })}
|
||||
{
|
||||
runnerStore.spec && <Runnables
|
||||
appState={appState}
|
||||
error={error}
|
||||
@@ -127,7 +119,7 @@ const Reporter: React.FC<SingleReporterProps> = observer(({ appState = appStateD
|
||||
studioEnabled={studioEnabled}
|
||||
canSaveStudioLogs={runnerStore.canSaveStudioLogs}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.testing-preferences {
|
||||
.testing-preferences-header {
|
||||
@include inner-header;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: $gray-700;
|
||||
|
||||
&::before {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.testing-preference {
|
||||
color: $gray-600;
|
||||
margin: 16px;
|
||||
|
||||
.testing-preference-header {
|
||||
color: $white;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { action } from 'mobx'
|
||||
import { observer } from 'mobx-react'
|
||||
import React from 'react'
|
||||
|
||||
import type { AppState } from '../lib/app-state'
|
||||
import defaultEvents, { Events } from '../lib/events'
|
||||
import Switch from '../lib/switch'
|
||||
|
||||
interface Props {
|
||||
events?: Events
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
const TestingPreferences: React.FC<Props> = observer(({
|
||||
events = defaultEvents,
|
||||
appState,
|
||||
}: Props) => {
|
||||
const toggleAutoScrollingUserPref = () => {
|
||||
appState.toggleAutoScrollingUserPref()
|
||||
events.emit('save:state')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="testing-preferences">
|
||||
<div className="testing-preferences-header">
|
||||
Testing Preferences
|
||||
</div>
|
||||
|
||||
<div className="testing-preference">
|
||||
<div className="testing-preference-header">
|
||||
Auto-scrolling
|
||||
<Switch
|
||||
data-cy="auto-scroll-switch"
|
||||
value={appState.autoScrollingUserPref}
|
||||
onUpdate={action('toggle:auto:scrolling', toggleAutoScrollingUserPref)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
Automatically scroll the command log while the tests are running.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
TestingPreferences.displayName = 'TestingPreferences'
|
||||
|
||||
export default TestingPreferences
|
||||
@@ -6,6 +6,8 @@ import { RunnablesStore } from './runnables-store'
|
||||
import { DebugDismiss } from '../header/DebugDismiss'
|
||||
import { Duration } from '../duration/duration'
|
||||
import { SpecFileName } from '../shared/SpecFileName'
|
||||
import { RunnablePopoverOptions } from './runnable-popover-options'
|
||||
import appState from '../lib/app-state'
|
||||
|
||||
const renderRunnableHeader = (children: ReactElement) => <div className="runnable-header" data-cy="runnable-header">{children}</div>
|
||||
|
||||
@@ -28,11 +30,14 @@ const RunnableHeader: React.FC<RunnableHeaderProps> = observer(({ spec, statsSto
|
||||
)
|
||||
}
|
||||
|
||||
const isStudioSingleTest = appState?.studioActive && appState.studioSingleTestActive
|
||||
|
||||
return renderRunnableHeader(
|
||||
<>
|
||||
<SpecFileName spec={spec} />
|
||||
{runnablesStore.testFilter && runnablesStore.totalTests > 0 && <DebugDismiss matched={runnablesStore.totalTests} total={runnablesStore.totalUnfilteredTests} />}
|
||||
<Duration duration={statsStore.duration} />
|
||||
{!isStudioSingleTest && <Duration duration={statsStore.duration} />}
|
||||
<RunnablePopoverOptions spec={spec} />
|
||||
</>,
|
||||
)
|
||||
})
|
||||
|
||||
206
packages/reporter/src/runnables/runnable-popover-options.tsx
Normal file
206
packages/reporter/src/runnables/runnable-popover-options.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { action } from 'mobx'
|
||||
import { observer } from 'mobx-react'
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import cs from 'classnames'
|
||||
|
||||
import Tooltip from '@cypress/react-tooltip'
|
||||
import Button from '@cypress-design/react-button'
|
||||
import { IconActionAddMedium, IconWindowCodeEditor, IconMenuDotsVertical } from '@cypress-design/react-icon'
|
||||
import defaultEvents, { Events } from '../lib/events'
|
||||
import Switch from '../lib/switch'
|
||||
import appState from '../lib/app-state'
|
||||
|
||||
interface Props {
|
||||
events?: Events
|
||||
spec: Cypress.Cypress['spec']
|
||||
}
|
||||
|
||||
export const RunnablePopoverOptions: React.FC<Props> = observer(({
|
||||
events = defaultEvents,
|
||||
spec,
|
||||
}: Props) => {
|
||||
const relativeSpecPath = spec.relative
|
||||
|
||||
const isStudioSingleTest = appState?.studioActive && appState.studioSingleTestActive
|
||||
|
||||
const fileDetails = {
|
||||
absoluteFile: spec.absolute,
|
||||
column: 0,
|
||||
displayFile: spec.name,
|
||||
line: 0,
|
||||
originalFile: relativeSpecPath,
|
||||
relativeFile: relativeSpecPath,
|
||||
}
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0 })
|
||||
const popoverRef = useRef<HTMLDivElement>(null)
|
||||
const buttonContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const togglePopover = () => {
|
||||
if (!isOpen && buttonContainerRef.current) {
|
||||
const rect = buttonContainerRef.current.getBoundingClientRect()
|
||||
|
||||
setPopoverPosition({
|
||||
top: rect.bottom + 4,
|
||||
left: rect.right - 250, // 250px is the popover width
|
||||
})
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
const handleOpenInIDE = () => {
|
||||
events.emit('open:file:unified', fileDetails)
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleNewTest = () => {
|
||||
events.emit('studio:init:suite', { suiteId: 'r1' })
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const toggleAutoScrollingUserPref = () => {
|
||||
appState.toggleAutoScrollingUserPref()
|
||||
events.emit('save:state')
|
||||
}
|
||||
|
||||
// TODO: to be implemented
|
||||
// const toggleShowHttpRequests = () => {}
|
||||
|
||||
// Close popover when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
isOpen &&
|
||||
popoverRef.current &&
|
||||
buttonContainerRef.current &&
|
||||
!popoverRef.current.contains(event.target as Node) &&
|
||||
!buttonContainerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
window.addEventListener('scroll', handleScroll, true)
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
window.removeEventListener('scroll', handleScroll, true)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const popoverContent = isOpen && (
|
||||
<div
|
||||
ref={popoverRef}
|
||||
className="runnable-popover"
|
||||
data-cy="more-options-runnable-popover"
|
||||
style={{
|
||||
top: `${popoverPosition.top}px`,
|
||||
left: `${popoverPosition.left}px`,
|
||||
}}
|
||||
>
|
||||
<div className="runnable-popover-section">
|
||||
<div className="runnable-popover-section-title">This spec</div>
|
||||
|
||||
<button
|
||||
className="runnable-popover-item"
|
||||
onClick={handleOpenInIDE}
|
||||
data-cy="runnable-popover-open-ide"
|
||||
>
|
||||
<IconWindowCodeEditor strokeColor="gray-500" fillColor="gray-500" />
|
||||
<span>Open in IDE</span>
|
||||
</button>
|
||||
|
||||
{!isStudioSingleTest && <button
|
||||
className="runnable-popover-item"
|
||||
onClick={handleNewTest}
|
||||
data-cy="runnable-popover-new-test"
|
||||
>
|
||||
<IconActionAddMedium strokeColor="gray-500" />
|
||||
<span>New test</span>
|
||||
</button>}
|
||||
</div>
|
||||
|
||||
<div className="runnable-popover-section">
|
||||
<div className="runnable-popover-section-title">Testing preferences</div>
|
||||
|
||||
{/* // TODO: to be implemented */}
|
||||
{/* <div className="runnable-popover-item-with-toggle">
|
||||
<div className="runnable-popover-item-with-toggle-content">
|
||||
<div className="runnable-popover-item-text">
|
||||
<span className="runnable-popover-item-label">Show HTTP requests</span>
|
||||
</div>
|
||||
<Switch
|
||||
data-cy="show-http-requests-switch"
|
||||
value={false}
|
||||
|
||||
onUpdate={action('toggle:show:http:requests', toggleShowHttpRequests)}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="runnable-popover-item-with-toggle">
|
||||
<div className="runnable-popover-item-with-toggle-content">
|
||||
<div className="runnable-popover-item-text">
|
||||
<span className="runnable-popover-item-label">Auto-scrolling</span>
|
||||
</div>
|
||||
<Switch
|
||||
data-cy="auto-scroll-switch"
|
||||
value={appState.autoScrollingUserPref}
|
||||
|
||||
onUpdate={action('toggle:auto:scrolling', toggleAutoScrollingUserPref)}
|
||||
/>
|
||||
</div>
|
||||
<span className="runnable-popover-item-description">
|
||||
Automatically scroll the command log while the tests are running.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const buttonComponent = () => (
|
||||
<div>
|
||||
<Button
|
||||
size="32"
|
||||
variant="outline-indigo"
|
||||
aria-label="Options"
|
||||
aria-expanded={isOpen}
|
||||
data-cy="runnable-options-button"
|
||||
onClick={togglePopover}
|
||||
className={cs('runnable-options-button', {
|
||||
'runnable-options-button-border': !isOpen,
|
||||
})}
|
||||
>
|
||||
<IconMenuDotsVertical className='runnable-options-button-icon' />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="runnable-popover-container" ref={buttonContainerRef}>
|
||||
{
|
||||
isOpen ? buttonComponent() : (
|
||||
<Tooltip placement='bottom' title={<p>Options</p>} className='cy-tooltip'>
|
||||
{buttonComponent()}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{isOpen && createPortal(popoverContent, document.body)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
@@ -579,3 +579,114 @@ $dotted-line-left-padding: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.runnable-popover-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.runnable-options-button {
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
justify-content: center;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
.runnable-options-button-border {
|
||||
border-color: rgb(255 255 255 / 0.2);
|
||||
}
|
||||
|
||||
.runnable-options-button-icon {
|
||||
rotate: 90deg;
|
||||
}
|
||||
}
|
||||
|
||||
.runnable-popover {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
width: 250px;
|
||||
max-width: 250px;
|
||||
padding: 8px;
|
||||
font-family: $font-system;
|
||||
|
||||
.runnable-popover-section {
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid $gray-50;
|
||||
}
|
||||
|
||||
.runnable-popover-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $gray-600;
|
||||
padding: 8px 6px 6px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.runnable-popover-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: $gray-900;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.runnable-popover-item-with-toggle {
|
||||
.runnable-popover-item-with-toggle-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
color: $gray-900;
|
||||
padding: 8px 6px;
|
||||
|
||||
.runnable-popover-item-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
|
||||
.runnable-popover-item-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.runnable-popover-item-description {
|
||||
font-size: 14px;
|
||||
color: $gray-700;
|
||||
line-height: 20px;
|
||||
font-weight: 400;
|
||||
padding: 0 6px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.switch {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react'
|
||||
import { getFilenameParts } from '../lib/util'
|
||||
import { OpenFileInIDEButton } from '../header/OpenFileInIDEButton'
|
||||
import { CreateNewTestButton } from '../header/CreateNewTestButton'
|
||||
|
||||
const displayFileName = (spec: Cypress.Cypress['spec']) => {
|
||||
const specParts = getFilenameParts(spec.name)
|
||||
@@ -27,7 +25,5 @@ export const SpecFileName = ({ spec }: { spec: Cypress.Cypress['spec'] }) => {
|
||||
|
||||
return <div className='spec-file-name'>
|
||||
{fileDetails.displayFile || fileDetails.originalFile}{!!fileDetails.line && `:${fileDetails.line}`}{!!fileDetails.column && `:${fileDetails.column}`}
|
||||
<OpenFileInIDEButton fileDetails={fileDetails} />
|
||||
<CreateNewTestButton suiteId='r1' dataCy='create-new-test-from-spec-header' />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RunnablesStore } from '../runnables/runnables-store'
|
||||
import { StatsStore } from '../header/stats-store'
|
||||
import Test from '../test/test-model'
|
||||
import scroller from '../lib/scroller'
|
||||
import events from '../lib/events'
|
||||
|
||||
describe('StudioTest', () => {
|
||||
let appState: AppState
|
||||
@@ -97,6 +98,8 @@ describe('StudioTest', () => {
|
||||
statsStore = {
|
||||
duration: 1500,
|
||||
} as unknown as StatsStore
|
||||
|
||||
cy.spy(events, 'emit').as('emitSpy')
|
||||
})
|
||||
|
||||
it('renders component with test information', () => {
|
||||
@@ -295,4 +298,21 @@ describe('StudioTest', () => {
|
||||
// Should not render anything when no test is available
|
||||
cy.get('.studio-single-test-container').should('not.exist')
|
||||
})
|
||||
|
||||
it('handles back button click', () => {
|
||||
cy.mount(
|
||||
<StudioTest
|
||||
appState={appState}
|
||||
runnablesStore={runnablesStore}
|
||||
statsStore={statsStore}
|
||||
/>,
|
||||
)
|
||||
|
||||
cy.get('[data-cy="studio-back-button"]').realHover()
|
||||
cy.get('.cy-tooltip').should('be.visible')
|
||||
cy.get('.cy-tooltip').should('contain.text', 'All tests')
|
||||
|
||||
cy.get('[data-cy="studio-back-button"]').click()
|
||||
cy.get('@emitSpy').should('have.been.calledWith', 'studio:cancel', undefined)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
.studio-header__test-section {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-bottom: 1px solid $gray-900;
|
||||
border: 1px solid $gray-900;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
padding: 16px;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
@@ -15,7 +17,8 @@
|
||||
flex-shrink: 0;
|
||||
|
||||
.state-icon {
|
||||
margin-top: 3px;
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
svg {
|
||||
@@ -30,8 +33,19 @@
|
||||
.studio-header__test-section-left {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
gap: 8px;
|
||||
margin-top: 5px;
|
||||
gap: 16px;
|
||||
|
||||
.studio-header__back-button {
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.studio-header__test-section-left-content {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-header__test-section-right {
|
||||
@@ -44,6 +58,8 @@
|
||||
color: $white;
|
||||
font-weight: 500;
|
||||
flex-grow: 1;
|
||||
min-height: 32px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.studio-header__test-tooltip-wrapper {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useRef } from 'react'
|
||||
import React, { useCallback, useMemo, useRef } from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
import { RunnablesStore } from '../runnables/runnables-store'
|
||||
import { Duration } from '../duration/duration'
|
||||
@@ -8,9 +8,11 @@ import Tooltip from '@cypress/react-tooltip'
|
||||
import cx from 'classnames'
|
||||
import Attempts from '../attempts/attempts'
|
||||
import { useScrollIntoView } from '../lib/useScrollIntoView'
|
||||
import { IconChevronDownSmall, IconStatusFailedSolid, IconStatusPassedSolid, IconStatusQueuedOutline, IconStatusRunningOutline } from '@cypress-design/react-icon'
|
||||
import { IconArrowLeft, IconChevronDownSmall, IconStatusFailedSolid, IconStatusPassedSolid, IconStatusQueuedOutline, IconStatusRunningOutline } from '@cypress-design/react-icon'
|
||||
import Test from '../test/test-model'
|
||||
import { StatsStore } from '../header/stats-store'
|
||||
import Button from '@cypress-design/react-button'
|
||||
import events from '../lib/events'
|
||||
|
||||
const getConnectors = (num: number) => {
|
||||
let connectors: JSX.Element[] = []
|
||||
@@ -70,6 +72,12 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St
|
||||
isStudioActive: appState.studioActive,
|
||||
})
|
||||
|
||||
const handleBackButton = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
events.emit('studio:cancel', undefined)
|
||||
}, [])
|
||||
|
||||
// Call callbackAfterUpdate when mounted and test changes
|
||||
React.useEffect(() => {
|
||||
if (isMounted && currentTest) {
|
||||
@@ -86,21 +94,32 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St
|
||||
<div className='studio-single-test-container' >
|
||||
<div className='studio-header__test-section'>
|
||||
<div className='studio-header__test-section-left'>
|
||||
<StatusIcon test={currentTest} />
|
||||
{parentTitles.length > 0 ? (
|
||||
<Tooltip title={<ul className='studio-tooltip__breadcrumb-list' ref={tooltipRef}>
|
||||
{getParentTitlesListElements(parentTitles)}
|
||||
</ul>}
|
||||
wrapperClassName='studio-header__test-tooltip-wrapper' className={cx(
|
||||
'studio-tooltip cy-tooltip',
|
||||
)}>
|
||||
{testTitle}
|
||||
</Tooltip>
|
||||
) : testTitle}
|
||||
|
||||
<Tooltip placement='bottom' title={<p>All tests</p>} className='cy-tooltip'>
|
||||
<div>
|
||||
<Button data-cy='studio-back-button' size='32' variant='outline-indigo' className='studio-header__back-button' onClick={handleBackButton}>
|
||||
<IconArrowLeft size='16' strokeColor='indigo-400' />
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div className='studio-header__test-section-left-content'>
|
||||
<StatusIcon test={currentTest} />
|
||||
{parentTitles.length > 0 ? (
|
||||
<Tooltip title={<ul className='studio-tooltip__breadcrumb-list' ref={tooltipRef}>
|
||||
{getParentTitlesListElements(parentTitles)}
|
||||
</ul>}
|
||||
wrapperClassName='studio-header__test-tooltip-wrapper' className={cx(
|
||||
'studio-tooltip cy-tooltip',
|
||||
)}>
|
||||
{testTitle}
|
||||
</Tooltip>
|
||||
) : testTitle}
|
||||
</div>
|
||||
</div>
|
||||
<div className='studio-header__test-section-right'>
|
||||
<Duration duration={statsStore.duration} />
|
||||
<Controls appState={appState} displayPreferencesButton={false} />
|
||||
<Controls appState={appState} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='studio-single-test-attempts' ref={containerRef}>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react'
|
||||
import { StudioTestHeader } from './StudioTestHeader'
|
||||
import events from '../lib/events'
|
||||
|
||||
describe('StudioTestHeader', () => {
|
||||
let mockSpec: Cypress.Cypress['spec']
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the spec
|
||||
mockSpec = {
|
||||
name: 'cypress/e2e/example.cy.ts',
|
||||
relative: 'cypress/e2e/example.cy.ts',
|
||||
absolute: '/Users/test/cypress/e2e/example.cy.ts',
|
||||
} as Cypress.Cypress['spec']
|
||||
|
||||
cy.spy(events, 'emit').as('emitSpy')
|
||||
})
|
||||
|
||||
it('renders studio header with spec information', () => {
|
||||
cy.mount(
|
||||
<StudioTestHeader
|
||||
spec={mockSpec}
|
||||
/>,
|
||||
)
|
||||
|
||||
cy.get('.studio-header').should('be.visible')
|
||||
cy.get('.studio-header__file-section').should('be.visible')
|
||||
cy.get('.spec-file-name').should('be.visible')
|
||||
cy.get('.spec-file-name').should('contain.text', 'example.cy.ts')
|
||||
cy.get('[data-cy="studio-back-button"]').should('be.visible')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('handles back button click', () => {
|
||||
cy.mount(
|
||||
<StudioTestHeader
|
||||
spec={mockSpec}
|
||||
/>,
|
||||
)
|
||||
|
||||
cy.get('[data-cy="studio-back-button"]').click()
|
||||
cy.get('@emitSpy').should('have.been.calledWith', 'studio:cancel', undefined)
|
||||
})
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
.studio-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
.studio-header__file-section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
padding: 0 16px;
|
||||
border-top: 1px solid $gray-900;
|
||||
border-bottom: 1px solid $gray-900;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: $gray-500;
|
||||
min-height: $header-height;
|
||||
|
||||
.studio-header__back-button {
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
import Button from '@cypress-design/react-button'
|
||||
import { IconArrowLeft } from '@cypress-design/react-icon'
|
||||
import events from '../lib/events'
|
||||
import { SpecFileName } from '../shared/SpecFileName'
|
||||
|
||||
interface StudioHeaderProps {
|
||||
spec: Cypress.Cypress['spec']
|
||||
}
|
||||
|
||||
export const StudioTestHeader = observer(({ spec }: StudioHeaderProps) => {
|
||||
const handleBackButton = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
events.emit('studio:cancel', undefined)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className='studio-header'>
|
||||
<div className='studio-header__file-section'>
|
||||
<Button data-cy='studio-back-button' size='32' variant='outline-dark' className='studio-header__back-button' onClick={handleBackButton}>
|
||||
<IconArrowLeft size='16' strokeColor='gray-500' />
|
||||
</Button>
|
||||
<SpecFileName spec={spec} />
|
||||
</div>
|
||||
</header>
|
||||
</>
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user