From 25a47aa5db4e7799322db830317401ec8107e6d5 Mon Sep 17 00:00:00 2001 From: Mark Noonan Date: Tue, 13 Sep 2022 09:17:23 -0400 Subject: [PATCH] fix: only adjust autoscrolling in interactive mode (#23053) Co-authored-by: Lachlan Miller --- packages/app/src/main.ts | 6 --- packages/reporter/cypress/e2e/runnables.cy.ts | 39 +++++++++++++++++-- packages/reporter/src/lib/scroller.ts | 28 +++++++++---- packages/reporter/src/main.tsx | 1 + packages/reporter/src/runnables/runnables.tsx | 23 ++++++++--- 5 files changed, 74 insertions(+), 23 deletions(-) diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts index 8b2063e54186..ded76864c6bd 100644 --- a/packages/app/src/main.ts +++ b/packages/app/src/main.ts @@ -12,12 +12,6 @@ import Toast, { POSITION } from 'vue-toastification' import 'vue-toastification/dist/index.css' import { createWebsocket, getRunnerConfigFromWindow } from './runner' -// set a global so we can run -// conditional code in the vite branch -// so that the existing runner code -// @ts-ignore -window.__vite__ = true - const app = createApp(App) const config = getRunnerConfigFromWindow() diff --git a/packages/reporter/cypress/e2e/runnables.cy.ts b/packages/reporter/cypress/e2e/runnables.cy.ts index 2f86da8ec6e6..718ea97efa7a 100644 --- a/packages/reporter/cypress/e2e/runnables.cy.ts +++ b/packages/reporter/cypress/e2e/runnables.cy.ts @@ -3,7 +3,9 @@ import { RootRunnable } from '../../src/runnables/runnables-store' import { itHandlesFileOpening } from '../support/utils' import type { BaseReporterProps } from '../../src/main' import type { RunnablesErrorModel } from '../../src/runnables/runnable-error' +import appState from '../../src/lib/app-state' import { MobxRunnerStore } from '@packages/app/src/store' +import scroller from '../../src/lib/scroller' const runnerStore = new MobxRunnerStore('e2e') @@ -16,7 +18,7 @@ runnerStore.setSpec({ describe('runnables', () => { let runner: EventEmitter let runnables: RootRunnable - let render: (renderProps?: Partial) => void + let render: (renderProps?: Partial, cypressMode?: 'run' | 'open') => void let start: (renderProps?: Partial) => Cypress.Chainable beforeEach(() => { @@ -26,12 +28,15 @@ describe('runnables', () => { runner = new EventEmitter() - render = (renderProps: Partial = {}) => { + render = (renderProps: Partial = {}, cypressMode = 'open') => { cy.visit('/').then((win: Cypress.AUTWindow) => { + win.__CYPRESS_MODE__ = cypressMode win.render({ runner, studioEnabled: renderProps.studioEnabled || false, runnerStore, + scroller, + appState, ...renderProps, }) }) @@ -183,14 +188,21 @@ describe('runnables', () => { }) describe('runnable-header (unified)', () => { + let spy: Cypress.Agent + beforeEach(() => { - cy.window().then((win) => win.__vite__ = true) + scroller.__setScrollThreholdMs(500) + spy = cy.spy(appState, 'temporarilySetAutoScrolling') start({ runnerStore, }) }) + afterEach(() => { + scroller.__reset() + }) + it('contains name of spec and emits when clicked', () => { const selector = '.runnable-header a' @@ -201,5 +213,26 @@ describe('runnables', () => { expect(runner.emit).to.be.calledWith('open:file:unified') }) }) + + it('adds a scroll listener in open mode', () => { + appState.startRunning() + cy.get('.container') + .trigger('scroll') + .trigger('scroll') + .trigger('scroll').then(() => { + expect(spy).to.have.been.calledWith(false) + }) + }) + + it('does not add a scroll listener in run mode', () => { + render({}, 'run') + appState.startRunning() + cy.get('.container') + .trigger('scroll') + .trigger('scroll') + .trigger('scroll').then(() => { + expect(spy).not.to.have.been.calledWith(false) + }) + }) }) }) diff --git a/packages/reporter/src/lib/scroller.ts b/packages/reporter/src/lib/scroller.ts index 0440faae704b..b926215636da 100644 --- a/packages/reporter/src/lib/scroller.ts +++ b/packages/reporter/src/lib/scroller.ts @@ -13,17 +13,17 @@ - element distance from top of container */ -import { TimeoutID } from './types' - -type UserScrollCallback = () => void +export type UserScrollCallback = () => void const PADDING = 100 +const SCROLL_THRESHOLD_MS = 50 export class Scroller { private _container: Element | null = null private _userScrollCount = 0 private _userScroll = true - private _countUserScrollsTimeout?: TimeoutID + private _countUserScrollsTimeout?: number + private _userScrollThresholdMs = SCROLL_THRESHOLD_MS setContainer (container: Element, onUserScroll?: UserScrollCallback) { this._container = container @@ -53,7 +53,7 @@ export class Scroller { onUserScroll() } - clearTimeout(this._countUserScrollsTimeout as TimeoutID) + clearTimeout(this._countUserScrollsTimeout) this._countUserScrollsTimeout = undefined this._userScrollCount = 0 @@ -62,10 +62,10 @@ export class Scroller { if (this._countUserScrollsTimeout) return - this._countUserScrollsTimeout = setTimeout(() => { + this._countUserScrollsTimeout = window.setTimeout(() => { this._countUserScrollsTimeout = undefined this._userScrollCount = 0 - }, 50) + }, this._userScrollThresholdMs) }) } @@ -129,8 +129,20 @@ export class Scroller { this._container = null this._userScroll = true this._userScrollCount = 0 - clearTimeout(this._countUserScrollsTimeout as TimeoutID) + clearTimeout(this._countUserScrollsTimeout) this._countUserScrollsTimeout = undefined + this._userScrollThresholdMs = SCROLL_THRESHOLD_MS + } + + __setScrollThreholdMs (ms: number) { + const isCypressInCypress = document.defaultView !== top + + // only allow this to be set in testing + if (!isCypressInCypress) { + return + } + + this._userScrollThresholdMs = ms } } diff --git a/packages/reporter/src/main.tsx b/packages/reporter/src/main.tsx index e47aa43ead0b..6d0a25f5cbcd 100644 --- a/packages/reporter/src/main.tsx +++ b/packages/reporter/src/main.tsx @@ -144,6 +144,7 @@ declare global { Cypress: any state: AppState render: ((props: Partial) => void) + __CYPRESS_MODE__: 'run' | 'open' } } diff --git a/packages/reporter/src/runnables/runnables.tsx b/packages/reporter/src/runnables/runnables.tsx index 47064a7084ca..80e59a18342a 100644 --- a/packages/reporter/src/runnables/runnables.tsx +++ b/packages/reporter/src/runnables/runnables.tsx @@ -9,7 +9,7 @@ import Runnable from './runnable-and-suite' import RunnableHeader from './runnable-header' import { RunnablesStore, RunnableArray } from './runnables-store' import statsStore, { StatsStore } from '../header/stats-store' -import { Scroller } from '../lib/scroller' +import { Scroller, UserScrollCallback } from '../lib/scroller' import type { AppState } from '../lib/app-state' import OpenFileInIDE from '../lib/open-file-in-ide' @@ -176,11 +176,22 @@ class Runnables extends Component { componentDidMount () { const { scroller, appState } = this.props - scroller.setContainer(this.refs.container as Element, action('user:scroll:detected', () => { - if (appState && appState.isRunning) { - appState.temporarilySetAutoScrolling(false) - } - })) + let maybeHandleScroll: UserScrollCallback | undefined = undefined + + if (window.__CYPRESS_MODE__ === 'open') { + // in open mode, listen for scroll events so that users can pause the command log auto-scroll + // by manually scrolling the command log + maybeHandleScroll = action('user:scroll:detected', () => { + if (appState && appState.isRunning) { + appState.temporarilySetAutoScrolling(false) + } + }) + } + + // we need to always call scroller.setContainer, but the callback can be undefined + // so we pass maybeHandleScroll. If we don't, Cypress blows up with an error like + // `A container must be set on the scroller with scroller.setContainer(container)` + scroller.setContainer(this.refs.container as Element, maybeHandleScroll) } }