import _ from 'lodash' import cs from 'classnames' import Markdown from 'markdown-it' import { action, observable } from 'mobx' import { observer } from 'mobx-react' import React, { Component, MouseEvent } from 'react' // @ts-ignore import Tooltip from '@cypress/react-tooltip' import appState, { AppState } from '../lib/app-state' import events, { Events } from '../lib/events' import FlashOnClick from '../lib/flash-on-click' import { TimeoutID } from '../lib/types' import runnablesStore, { RunnablesStore } from '../runnables/runnables-store' import { Alias, AliasObject } from '../instruments/instrument-model' import CommandModel from './command-model' import TestError from '../errors/test-error' const md = new Markdown() const displayName = (model: CommandModel) => model.displayName || model.name const nameClassName = (name: string) => name.replace(/(\s+)/g, '-') const formattedMessage = (message: string) => message ? md.renderInline(message) : '' const visibleMessage = (model: CommandModel) => { if (model.visible) return '' return model.numElements > 1 ? 'One or more matched elements are not visible.' : 'This element is not visible.' } const shouldShowCount = (aliasesWithDuplicates: Array | null, aliasName: Alias, model: CommandModel) => { if (model.aliasType !== 'route') { return false } return _.includes(aliasesWithDuplicates, aliasName) } interface AliasReferenceProps { aliasObj: AliasObject model: CommandModel aliasesWithDuplicates: Array | null } const AliasReference = observer(({ aliasObj, model, aliasesWithDuplicates }: AliasReferenceProps) => { if (shouldShowCount(aliasesWithDuplicates, aliasObj.name, model)) { return ( @{aliasObj.name} {aliasObj.cardinal} ) } return ( @{aliasObj.name} ) }) interface AliasesReferencesProps { model: CommandModel aliasesWithDuplicates: Array | null } const AliasesReferences = observer(({ model, aliasesWithDuplicates }: AliasesReferencesProps) => ( {_.map(([] as Array).concat((model.referencesAlias as AliasObject)), (aliasObj) => ( ))} )) interface InterceptionsProps { model: CommandModel } const Interceptions = observer(({ model }: InterceptionsProps) => { if (!model.renderProps.interceptions?.length) return null function getTitle () { return ( {model.renderProps.wentToOrigin ? '' : <>This request did not go to origin because the response was stubbed.
} This request matched:
    {model.renderProps.interceptions?.map(({ command, alias, type }, i) => { return (
  • cy.{command}() {type} with {alias ? <>alias @{alias} : 'no alias'}
  • ) })}
) } const count = model.renderProps.interceptions.length const displayAlias = _.chain(model.renderProps.interceptions).last().get('alias').value() return ( 1 && 'show-count')}>{model.renderProps.status ? {model.renderProps.status} : null}{displayAlias || no alias} {count > 1 ? {count} : null} ) }) interface AliasesProps { isOpen: boolean model: CommandModel aliasesWithDuplicates: Array | null } const Aliases = observer(({ model, aliasesWithDuplicates, isOpen }: AliasesProps) => { if (!model.alias || model.aliasType === 'route') return null return ( {_.map(([] as Array).concat(model.alias), (alias) => { const aliases = [alias] if (!isOpen && model.hasChildren) { aliases.push(..._.compact(model.children.map((dupe) => dupe.alias))) } return ( `'${alias}'`).join(', ')}`} className='cy-tooltip'> {aliases.join(', ')} ) })} ) }) interface MessageProps { model: CommandModel } const Message = observer(({ model }: MessageProps) => ( )) interface ProgressProps { model: CommandModel } const Progress = observer(({ model }: ProgressProps) => { if (!model.timeout || !model.wallClockStartedAt) { return
} const timeElapsed = Date.now() - new Date(model.wallClockStartedAt).getTime() const timeRemaining = model.timeout ? model.timeout - timeElapsed : 0 const percentageRemaining = timeRemaining / model.timeout || 0 // we add a key to the span to ensure a rerender and restart of the animation on change return (
) }) interface Props { model: CommandModel aliasesWithDuplicates: Array | null appState: AppState events: Events runnablesStore: RunnablesStore groupId?: number } @observer class Command extends Component { @observable isOpen: boolean|null = null private _showTimeout?: TimeoutID static defaultProps = { appState, events, runnablesStore, } render () { const { model, aliasesWithDuplicates } = this.props const message = model.displayMessage if (model.group && this.props.groupId !== model.group) { return null } if (model.showError) { return } return (
  • 100, 'no-elements': !model.numElements, 'command-has-snapshot': model.hasSnapshot, 'command-has-console-props': model.hasConsoleProps, 'multiple-elements': model.numElements > 1, 'command-has-children': model.hasChildren, 'command-is-child': model.isChild, 'command-is-open': this._isOpen(), }, )} >
    this._snapshot(true)} onMouseLeave={() => this._snapshot(false)} >
    {model.number || ''} {model.event && model.type !== 'system' ? `(${displayName(model)})` : displayName(model)} {model.referencesAlias ? : } {model.numElements} 1 })}>{model.numChildren}
    {this._children()}
  • ) } _isOpen () { const { model } = this.props return !!model.isOpen } _children () { const { appState, events, model, runnablesStore } = this.props if (!this._isOpen()) return return (
      {_.map(model.children, (child) => ( ))}
    ) } _isPinned () { return this.props.appState.pinnedSnapshotId === this.props.model.id } _shouldShowClickMessage = () => { if (this.props.model.hasChildren) { return false } return !this.props.appState.isRunning && !!this.props.model.hasConsoleProps } @action _onClick = () => { if (this.props.model.hasChildren) { this.props.model.toggleOpen() return } if (this.props.appState.isRunning || this.props.appState.studioActive) return const { id } = this.props.model if (this._isPinned()) { this.props.appState.pinnedSnapshotId = null this.props.events.emit('unpin:snapshot', id) this._snapshot(true) } else { this.props.appState.pinnedSnapshotId = id as number this.props.events.emit('pin:snapshot', id) this.props.events.emit('show:command', this.props.model.id) } } // snapshot rules // // 1. when we hover over a command, wait 50 ms // if we're still hovering, send show:snapshot // // 2. when we hover off a command, wait 50 ms // and if we are still in a non-showing state // meaning we have moused over nothing instead // of a different command, send hide:snapshot // // this prevents heavy CPU usage when hovering // up and down over commands. it also prevents // restoring to the original through all of that. // additionally when quickly moving your mouse // over many commands, unless you're hovered for // 50ms, it won't show the snapshot at all. so we // optimize for both snapshot showing + restoring _snapshot (show: boolean) { const { model, runnablesStore } = this.props if (show) { runnablesStore.attemptingShowSnapshot = true this._showTimeout = setTimeout(() => { runnablesStore.showingSnapshot = true this.props.events.emit('show:snapshot', model.id) }, 50) } else { runnablesStore.attemptingShowSnapshot = false clearTimeout(this._showTimeout as TimeoutID) setTimeout(() => { // if we are currently showing a snapshot but // we aren't trying to show a different snapshot if (runnablesStore.showingSnapshot && !runnablesStore.attemptingShowSnapshot) { runnablesStore.showingSnapshot = false this.props.events.emit('hide:snapshot', model.id) } }, 50) } } _removeStudioCommand = (e: MouseEvent) => { e.preventDefault() e.stopPropagation() const { model, events } = this.props if (!model.isStudio) return events.emit('studio:remove:command', model.number) } } export { Aliases, AliasesReferences, Message, Progress } export default Command