chore: Migrate AssertionsOptions menu to Vue. (#24286)

* React to vue

* Goodbye dom.

* Use event modifier.

* Remove leading underscore in names

* Use modifier.

* Add flip, shift options.

* Use createPopper instead of floating-ui.

* Remove unnecessary dependencies.

* addAssertion to event.

* setPopperElement to event.

* Strong types.

* clarify test.

Co-authored-by: Blue F <blue@cypress.io>
This commit is contained in:
Kukhyeon Heo
2022-12-24 06:28:41 +09:00
committed by GitHub
parent 51f30a2a34
commit af0da61b8c
19 changed files with 574 additions and 543 deletions
@@ -255,4 +255,27 @@ it('visits a basic html page', () => {
// Cypress in Cypress, it redirects us the the spec page, which is not what normally
// would happen in production.
})
it('shows menu and submenu correctly', () => {
launchStudio()
cy.getAutIframe().within(() => {
// Show menu
cy.get('h1').realClick({
button: 'right',
})
cy.get('.__cypress-studio-assertions-menu').shadow()
.find('.assertions-menu').should('be.visible')
// Show submenu
cy.get('.__cypress-studio-assertions-menu').shadow()
.find('.assertion-type-text:first').realHover()
cy.get('.__cypress-studio-assertions-menu').shadow()
.find('.assertion-option')
.should('have.text', 'Hello, Studio!')
.should('be.visible')
})
})
})
+1
View File
@@ -29,6 +29,7 @@
"@intlify/vite-plugin-vue-i18n": "2.4.0",
"@packages/frontend-shared": "0.0.0-development",
"@percy/cypress": "^3.1.0",
"@popperjs/core": "2.11.6",
"@testing-library/cypress": "BlueWinds/cypress-testing-library#119054b5963b0d2e064b13c5cc6fc9db32c8b7b5",
"@types/faker": "5.5.8",
"@urql/core": "2.4.4",
@@ -0,0 +1,96 @@
<template>
<div
ref="popper"
class="assertion-options"
>
<div
v-for="{ name, value } in options"
:key="`${name}${value}`"
class="assertion-option"
@click.stop="() => onClick(name, value)"
>
<span
v-if="name"
class="assertion-option-name"
>
{{ truncate(name) }}:{{ ' ' }}
</span>
<span
v-else
class="assertion-option-value"
>
{{ typeof value === 'string' && truncate(value) }}
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { createPopper } from '@popperjs/core'
import { onMounted, ref, nextTick, Ref } from 'vue'
import type { AssertionOption } from './types'
const props = defineProps<{
type: string
options: AssertionOption[]
}>()
const emit = defineEmits<{
(eventName: 'addAssertion', value: { type: string, name: string, value: string })
(eventName: 'setPopperElement', value: HTMLElement)
}>()
const truncate = (str: string) => {
if (str && str.length > 80) {
return `${str.substr(0, 77)}...`
}
return str
}
const popper: Ref<HTMLElement | null> = ref(null)
onMounted(() => {
nextTick(() => {
const popperEl = popper.value as HTMLElement
const reference = popperEl.parentElement as HTMLElement
createPopper(reference, popperEl, {
placement: 'right-start',
})
emit('setPopperElement', popperEl)
})
})
const onClick = (name, value) => {
emit('addAssertion', { type: props.type, name, value })
}
</script>
<style lang="scss">
@import './assertions-style.scss';
.assertion-options {
@include menu-style;
font-size: 14px;
max-width: 150px;
overflow: hidden;
overflow-wrap: break-word;
position: absolute;
.assertion-option {
cursor: pointer;
padding: 0.4rem 0.6rem;
&:hover {
background-color: #e9ecef;
}
.assertion-option-value {
font-weight: 600;
}
}
}
</style>
@@ -0,0 +1,123 @@
<template>
<div
:class="['assertion-type', { 'single-assertion': !hasOptions }]"
@click.stop="onClick"
@mouseover.stop="onOpen"
@mouseout.stop="onClose"
>
<div class="assertion-type-text">
<span>
{{ type.replace(/\./g, ' ') }}
</span>
<span
v-if="hasOptions"
class="dropdown-arrow"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"
/>
</svg>
</span>
</div>
<AssertionOptions
v-if="hasOptions && isOpen"
:type="type"
:options="options"
@set-popper-element="setPopperElement"
@add-assertion="addAssertion"
/>
</div>
</template>
<script lang="ts" setup>
import { Ref, ref } from 'vue'
import AssertionOptions from './AssertionOptions.ce.vue'
const props = defineProps<{
type: string
options: any
}>()
const emit = defineEmits<{
(eventName: 'addAssertion', value: { type: string, name?: string, value?: string })
}>()
const isOpen = ref(false)
const hasOptions = props.options && !!props.options.length
const popperElement: Ref<HTMLElement | null> = ref(null)
const onOpen = () => {
isOpen.value = true
}
const onClose = (e: MouseEvent) => {
if (e.relatedTarget instanceof Element &&
popperElement.value && popperElement.value.contains(e.relatedTarget)) {
return
}
isOpen.value = false
}
const onClick = () => {
if (!hasOptions) {
emit('addAssertion', { type: props.type })
}
}
const setPopperElement = (el: HTMLElement) => {
popperElement.value = el
}
const addAssertion = ({ type, name, value }) => {
emit('addAssertion', { type, name, value })
}
</script>
<style lang="scss">
@import './assertions-style.scss';
.assertion-type {
color: #202020;
cursor: default;
font-size: 14px;
padding: 0.4rem 0.4rem 0.4rem 0.7rem;
position: static;
&:first-of-type {
padding-top: 0.5rem;
}
&:last-of-type {
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
padding-bottom: 0.5rem;
}
&:hover {
background-color: #e9ecef;
}
&.single-assertion {
cursor: pointer;
font-weight: 600;
}
.assertion-type-text {
align-items: center;
display: flex;
.dropdown-arrow {
margin-left: auto;
}
}
}
</style>
@@ -0,0 +1,161 @@
<template>
<div
ref="highlight"
class="highlight"
:style="highlightStyle"
/>
<div
ref="assertionsMenu"
class="assertions-menu"
>
<div class="header">
<div class="title">
<span>Add Assertion</span>
</div>
<div class="close-wrapper">
<a
class="close"
@click.stop="onClose"
>&times;</a>
</div>
</div>
<div
class="subtitle"
>
expect
{{ ' ' }}
<code>
{{ tagName }}
</code>
{{ ' ' }}
to
</div>
<div
class="assertions-list"
>
<AssertionType
v-for="(assertion) in possibleAssertions"
:key="assertion.type"
:type="assertion.type"
:options="assertion.options"
@add-assertion="onAddAssertion"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { createPopper } from '@popperjs/core'
import AssertionType from './AssertionType.ce.vue'
import _ from 'lodash'
import { nextTick, onMounted, Ref, ref, StyleValue } from 'vue'
import type { PossibleAssertions, AddAssertion, AssertionArgs } from './types'
const props = defineProps <{
jqueryElement: JQuery<HTMLElement>
possibleAssertions: PossibleAssertions
addAssertion: AddAssertion
closeMenu: () => void
highlightStyle: StyleValue
}>()
const onAddAssertion = ({ type, name, value }: {
type: string
name?: string
value?: string
}) => {
let args = [type, name, value]
args = _.compact(args)
props.addAssertion(props.jqueryElement, ...args as AssertionArgs)
}
const onClose = () => {
props.closeMenu()
}
const tagName = `<${props.jqueryElement.prop('tagName').toLowerCase()}>`
const highlight: Ref<HTMLElement | null> = ref(null)
const assertionsMenu: Ref<HTMLElement | null> = ref(null)
onMounted(() => {
nextTick(() => {
const highlightEl = highlight.value as HTMLElement
const assertionsMenuEl = assertionsMenu.value as HTMLElement
createPopper(highlightEl, assertionsMenuEl, {
modifiers: [
{
name: 'preventOverflow',
options: {
altAxis: true,
},
},
],
})
})
})
</script>
<style lang="scss">
@import "./assertions-style.scss";
.highlight {
background: rgba(159, 196, 231, 0.6);
border: solid 2px #9FC4E7;
cursor: pointer;
}
.assertions-menu {
@include menu-style;
font-family: 'Helvetica Neue', 'Arial', sans-serif;
z-index: 2147483647;
width: 175px;
position: absolute;
.header {
align-items: center;
background: #07b282;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
color: #fff;
display: flex;
padding: 0.5rem 0.7rem;
.title {
font-size: 14px;
font-weight: 600;
}
.close-wrapper {
margin-left: auto;
margin-top: -2.5px;
.close {
font-size: 18px;
font-weight: 500;
&:hover, &:focus, &:active {
cursor: pointer;
color: #eee;
}
}
}
}
.subtitle {
border-bottom: 1px solid #c4c4c4;
color: #6b6b6b;
font-size: 13px;
font-style: italic;
font-weight: 400;
padding: 0.5rem 0.7rem;
code {
font-weight: 600;
}
}
}
</style>
@@ -0,0 +1,8 @@
$border-radius: 4px;
@mixin menu-style {
background: #fff;
border: 1px solid #DDD;
box-shadow: 2px 5px 12px rgba(0, 0, 0, 0.2);
border-radius: $border-radius;
}
+133
View File
@@ -0,0 +1,133 @@
import { App, createApp, StyleValue } from 'vue'
import AssertionsMenu from './AssertionsMenu.ce.vue'
import AssertionType from './AssertionType.ce.vue'
import AssertionOptions from './AssertionOptions.ce.vue'
import { getOrCreateHelperDom, getSelectorHighlightStyles } from '../dom'
import type { PossibleAssertions, AddAssertion } from './types'
function getStudioAssertionsMenuDom (body) {
return getOrCreateHelperDom({
body,
className: '__cypress-studio-assertions-menu',
css: `${AssertionsMenu.styles}\n${AssertionType.styles}\n${AssertionOptions.styles}`,
})
}
interface StudioAssertionsMenuArgs {
$el: JQuery<HTMLElement>
$body: JQuery<HTMLElement>
props: {
possibleAssertions: PossibleAssertions
addAssertion: AddAssertion
closeMenu: () => void
}
}
export function openStudioAssertionsMenu ({ $el, $body, props }: StudioAssertionsMenuArgs) {
const { vueContainer } = getStudioAssertionsMenuDom($body.get(0))
vueContainerListeners(vueContainer)
const selectorHighlightStyles = getSelectorHighlightStyles([$el.get(0)])[0]
mountAssertionsMenu(vueContainer, $el, props.possibleAssertions, props.addAssertion, props.closeMenu, selectorHighlightStyles)
}
export function closeStudioAssertionsMenu ($body) {
const { container } = getStudioAssertionsMenuDom($body.get(0))
unmountAssertionsMenu()
container.remove()
}
let app: App<Element> | null = null
const mountAssertionsMenu = (
container: Element,
jqueryElement: any,
possibleAssertions: any[],
addAssertion: any,
closeMenu: any,
highlightStyle: StyleValue,
) => {
app = createApp(AssertionsMenu, {
jqueryElement,
possibleAssertions,
addAssertion,
closeMenu,
highlightStyle,
})
app.mount(container)
}
const unmountAssertionsMenu = () => {
if (app) {
app.unmount()
app = null
}
}
// TODO: remove these.
// For some reason, the root div of our AssertionsMenu app usually gets
// all the events and does not distribute the events to the children.
// So, we're manually distributing the events.
// But it causes duplicated events are sent to the same object, so we're filtering them.
// I failed to prove it's our problem or Vue's problem.
let lastTarget = null
let lastTimeStamp = -1
function vueContainerListeners (vueContainer) {
vueContainer.addEventListener('click', (e) => {
const paths = e.composedPath()
for (let i = 0; i < paths.length; i++) {
const el = paths[i] as HTMLElement
if (classIncludes(el, 'single-assertion') ||
classIncludes(el, 'assertion-option') ||
(el.tagName === 'A' && classIncludes(el, 'close'))) {
el.dispatchEvent(new MouseEvent('click', e))
break
}
}
})
vueContainer.addEventListener('mouseover', (e) => {
const paths = e.composedPath()
for (let i = 0; i < paths.length; i++) {
const el = paths[i] as HTMLElement
if (classIncludes(el, 'assertion-type')) {
el.dispatchEvent(new MouseEvent('mouseover', e))
break
}
}
})
vueContainer.addEventListener('mouseout', (e) => {
// Sometimes, there is maximum call stack size exceeded error.
if (lastTarget === e.target && lastTimeStamp - e.timeStamp < 100) {
return
}
lastTarget = e.target
lastTimeStamp = e.timeStamp
const paths = e.composedPath()
for (let i = 0; i < paths.length; i++) {
const el = paths[i] as HTMLElement
if (classIncludes(el, 'assertion-type')) {
el.dispatchEvent(new MouseEvent('mouseout', e))
break
}
}
})
}
function classIncludes (el, className) {
return typeof el.className === 'string' && el.className.includes(className)
}
+17
View File
@@ -0,0 +1,17 @@
export interface AssertionOption {
name?: string
value?: string | number | string[]
}
export type PossibleAssertions = Array<{ type: string, options?: AssertionOption[] }>
// Single argument assertion: ['be.visible']
type AssertionArgs_1 = [string]
// Two argument assertion: ['have.text', '<some text>']
type AssertionArgs_2 = [string, string]
// Three argument assertion: ['have.attr', 'href', '<some value>']
type AssertionArgs_3 = [string, string, string]
export type AssertionArgs = AssertionArgs_1 | AssertionArgs_2 | AssertionArgs_3
export type AddAssertion = ($el: HTMLElement | JQuery<HTMLElement>, ...args: AssertionArgs) => void
+7 -14
View File
@@ -3,7 +3,9 @@ import { defineStore } from 'pinia'
import { getEventManager } from '../runner'
import type { StudioSavePayload } from '../runner/event-manager-types'
import { closeStudioAssertionsMenu, openStudioAssertionsMenu } from '../runner/studio/mounter'
import { useAutStore } from './aut-store'
import type { PossibleAssertions, AssertionArgs } from '../runner/studio/types'
function getCypress () {
const eventManager = getEventManager()
@@ -86,15 +88,6 @@ const tagNamesWithValue = [
'TEXTAREA',
]
// Single argument assertion: ['be.visible']
type AssertionArgs_1 = [string]
// Two argument assertion: ['have.text', '<some text>']
type AssertionArgs_2 = [string, string]
// Three argument assertion: ['have.attr', 'href', '<some value>']
type AssertionArgs_3 = [string, string, string]
type AssertionArgs = AssertionArgs_1 | AssertionArgs_2 | AssertionArgs_3
export interface StudioLog {
id?: number
name: string
@@ -402,7 +395,7 @@ export const useStudioStore = defineStore('studioRecorder', {
})
},
_addAssertion ($el: HTMLElement, ...args: AssertionArgs) {
_addAssertion ($el: HTMLElement | JQuery<HTMLElement>, ...args: AssertionArgs) {
const id = this._getId()
const selector = getCypress().SelectorPlayground.getSelector($el)
@@ -420,7 +413,7 @@ export const useStudioStore = defineStore('studioRecorder', {
id,
selector,
name: 'assert',
message: this._generateAssertionMessage($el, ...args),
message: this._generateAssertionMessage($el as HTMLElement, ...args),
}
this._generateBothLogs(reporterLog).forEach((commandLog) => {
@@ -774,7 +767,7 @@ export const useStudioStore = defineStore('studioRecorder', {
this._closeAssertionsMenu()
window.UnifiedRunner.dom.openStudioAssertionsMenu({
openStudioAssertionsMenu({
$el,
$body: window.UnifiedRunner.CypressJQuery(this._body),
props: {
@@ -790,13 +783,13 @@ export const useStudioStore = defineStore('studioRecorder', {
throw Error('this._body was not defined')
}
window.UnifiedRunner.dom.closeStudioAssertionsMenu(window.UnifiedRunner.CypressJQuery(this._body))
closeStudioAssertionsMenu(window.UnifiedRunner.CypressJQuery(this._body))
},
_generatePossibleAssertions ($el: JQuery<Element>) {
const tagName = $el.prop('tagName')
const possibleAssertions: Array<{ type: string, options?: unknown[] }> = []
const possibleAssertions: PossibleAssertions = []
if (!tagNamesWithoutText.includes(tagName)) {
const text = $el.text()
-20
View File
@@ -14,29 +14,9 @@
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, ."
},
"devDependencies": {
"@cypress/react-tooltip": "0.5.3",
"@fontsource/mulish": "4.3.0",
"@fontsource/open-sans": "4.3.0",
"@fortawesome/fontawesome-free": "6.0.0",
"@packages/driver": "0.0.0-development",
"@packages/icons": "0.0.0-development",
"@packages/reporter": "0.0.0-development",
"@packages/web-config": "0.0.0-development",
"@popperjs/core": "2.9.2",
"babel-plugin-prismjs": "1.0.2",
"bluebird": "3.5.3",
"classnames": "2.3.1",
"cross-env": "6.0.3",
"jquery": "3.1.1",
"lodash": "^4.17.21",
"mobx": "5.15.4",
"mobx-react": "6.1.8",
"prismjs": "1.21.0",
"prop-types": "15.7.2",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-popper": "2.2.5",
"react-shadow-dom-retarget-events": "1.0.11",
"rimraf": "3.0.2",
"webpack": "^4.44.2",
"webpack-cli": "3.3.2"
-102
View File
@@ -1,102 +0,0 @@
import _ from 'lodash'
import retargetEvents from 'react-shadow-dom-retarget-events'
import $Cypress from '@packages/driver'
import { studioAssertionsMenu } from '../studio/assertions-menu'
// The '!' tells webpack to disable normal loaders, and keep loaders with `enforce: 'pre'` and `enforce: 'post'`
// This disables the CSSExtractWebpackPlugin and allows us to get the CSS as a raw string instead of saving it to a separate file.
import studioAssertionsMenuCSS from '!../studio/assertions-menu.scss'
const $ = $Cypress.$
function getOrCreateHelperDom ({ $body, className, css }) {
let $container = $body.find(`.${className}`)
if ($container.length) {
const shadowRoot = $container[0].shadowRoot
return {
$container,
shadowRoot,
$reactContainer: $(shadowRoot).find('.react-container'),
}
}
$container = $('<div />')
.addClass(className)
.css({ position: 'static' })
.appendTo($body)
const shadowRoot = $container[0].attachShadow({ mode: 'open' })
const $reactContainer = $('<div />')
.addClass('react-container')
.appendTo(shadowRoot)
$('<style />', { html: css.toString() }).prependTo(shadowRoot)
return { $container, shadowRoot, $reactContainer }
}
function getSelectorHighlightStyles ($el) {
const borderSize = 2
return $el.map((__, el) => {
const $el = $(el)
const offset = $el.offset()
return {
position: 'absolute',
margin: 0,
padding: 0,
width: $el.outerWidth(),
height: $el.outerHeight(),
top: offset.top - borderSize,
left: offset.left - borderSize,
transform: $el.css('transform'),
zIndex: getZIndex($el),
}
}).get()
}
function getStudioAssertionsMenuDom ($body) {
return getOrCreateHelperDom({
$body,
className: '__cypress-studio-assertions-menu',
css: studioAssertionsMenuCSS,
})
}
function openStudioAssertionsMenu ({ $el, $body, props }) {
const { shadowRoot, $reactContainer } = getStudioAssertionsMenuDom($body)
const selectorHighlightStyles = getSelectorHighlightStyles($el)[0]
studioAssertionsMenu.render($reactContainer[0], {
$el,
selectorHighlightStyles,
...props,
})
retargetEvents(shadowRoot)
}
function closeStudioAssertionsMenu ($body) {
const { $container, $reactContainer } = getStudioAssertionsMenuDom($body)
studioAssertionsMenu.unmount($reactContainer[0])
$container.remove()
}
function getZIndex (el) {
if (/^(auto|0)$/.test(el.css('zIndex'))) {
return 2147483647
}
return _.toNumber(el.css('zIndex'))
}
export const dom = {
openStudioAssertionsMenu,
closeStudioAssertionsMenu,
}
-1
View File
@@ -1 +0,0 @@
export * from './dom'
-2
View File
@@ -1,8 +1,6 @@
@import 'lib/variables';
@import 'lib/mixins';
@import 'lib/fonts';
$cy-tooltip-class: 'cy-tooltip';
@import '~@cypress/react-tooltip/dist/tooltip.scss';
@import 'lib/base';
// import all other scss files in src except if they are in lib
// or their file name is `main`
@@ -1,149 +0,0 @@
import React, { useState } from 'react'
import { render, unmountComponentAtNode } from 'react-dom'
import { usePopper } from 'react-popper'
import _ from 'lodash'
import cs from 'classnames'
const AssertionType = ({ addAssertion, type, options }) => {
const [isOpen, setOpen] = useState(false)
const [referenceElement, setReferenceElement] = useState(null)
const [popperElement, setPopperElement] = useState(null)
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'right-start',
})
function _open () {
setOpen(true)
}
function _close (e) {
// don't close menu if mouse out to sub menu
if (popperElement && popperElement.contains(e.relatedTarget)) {
return
}
setOpen(false)
}
function _truncate (str) {
if (str && str.length > 80) {
return `${str.substr(0, 77)}...`
}
return str
}
const hasOptions = options && !!options.length
return (
<div
ref={setReferenceElement}
className={cs('assertion-type', { 'single-assertion': !hasOptions })}
onClick={!hasOptions ? () => addAssertion(type) : null}
// onMouseEnter and onMouseLeave events do not work properly inside shadow dom
onMouseOver={_open}
onMouseOut={_close}
>
<div className='assertion-type-text'>
<span>
{type.replace(/\./g, ' ')}
</span>
{hasOptions && (
<span className='dropdown-arrow'>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path fillRule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" />
</svg>
</span>
)}
</div>
{hasOptions && isOpen && (
<div ref={setPopperElement} className='assertion-options' style={styles.popper} {...attributes.popper}>
{options.map(({ name, value }) => (
<div
key={`${name}${value}`}
className='assertion-option'
onClick={() => addAssertion(type, name, value)}
>
{name && (
<span className='assertion-option-name'>
{_truncate(name)}
:
{' '}
</span>
)}
<span className='assertion-option-value'>
{_truncate(value)}
</span>
</div>
))}
</div>
)}
</div>
)
}
const AssertionsMenu = ({ $el, possibleAssertions, addAssertion, closeMenu, selectorHighlightStyles }) => {
const [referenceElement, setReferenceElement] = useState(null)
const [popperElement, setPopperElement] = useState(null)
const { styles, attributes } = usePopper(referenceElement, popperElement, {
modifiers: [{
name: 'preventOverflow',
options: {
altAxis: true,
},
}],
})
function _addAssertion (...args) {
args = _.compact(args)
addAssertion($el, ...args)
}
function _close (event) {
event.preventDefault()
closeMenu()
}
return (
<>
<div ref={setReferenceElement} className='highlight' style={selectorHighlightStyles} />
<div ref={setPopperElement} className='assertions-menu' style={styles.popper} {...attributes.popper}>
<div className='header'>
<div className='title'>
<span>Add Assertion</span>
</div>
<div className='close-wrapper'>
<a className='close' onClick={_close}>&times;</a>
</div>
</div>
<div className='subtitle'>
expect
{' '}
<code>
{`<${$el.prop('tagName').toLowerCase()}>`}
</code>
{' '}
to
</div>
<div className='assertions-list'>
{possibleAssertions.map((assertion) => (
<AssertionType key={assertion.type} addAssertion={_addAssertion} {...assertion} />
))}
</div>
</div>
</>
)
}
const renderAssertionsMenu = (container, props) => {
render(<AssertionsMenu {...props} />, container)
}
export const studioAssertionsMenu = {
render: renderAssertionsMenu,
unmount: unmountComponentAtNode,
}
@@ -1,130 +0,0 @@
/**
* This is a standalone file that gets included with the assertions menu
* in the user's app html
*/
$border-radius: 4px;
@mixin menu-style {
background: #fff;
border: 1px solid #DDD;
box-shadow: 2px 5px 12px rgba(0, 0, 0, 0.2);
border-radius: $border-radius;
}
.highlight {
background: rgba(159, 196, 231, 0.6);
border: solid 2px #9FC4E7;
cursor: pointer;
}
.assertions-menu {
@include menu-style;
font-family: 'Helvetica Neue', 'Arial', sans-serif;
z-index: 2147483647;
width: 175px;
.header {
align-items: center;
background: #07b282;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
color: #fff;
display: flex;
padding: 0.5rem 0.7rem;
.title {
font-size: 14px;
font-weight: 600;
}
.close-wrapper {
margin-left: auto;
margin-top: -2.5px;
.close {
font-size: 18px;
font-weight: 500;
&:hover, &:focus, &:active {
cursor: pointer;
color: #eee;
}
}
}
}
.subtitle {
border-bottom: 1px solid #c4c4c4;
color: #6b6b6b;
font-size: 13px;
font-style: italic;
font-weight: 400;
padding: 0.5rem 0.7rem;
code {
font-weight: 600;
}
}
.assertions-list {
.assertion-type {
color: #202020;
cursor: default;
font-size: 14px;
padding: 0.4rem 0.4rem 0.4rem 0.7rem;
position: relative;
&:first-of-type {
padding-top: 0.5rem;
}
&:last-of-type {
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
padding-bottom: 0.5rem;
}
&:hover {
background-color: #e9ecef;
}
&.single-assertion {
cursor: pointer;
font-weight: 600;
}
.assertion-type-text {
align-items: center;
display: flex;
.dropdown-arrow {
margin-left: auto;
}
}
.assertion-options {
@include menu-style;
font-size: 14px;
max-width: 150px;
overflow: hidden;
overflow-wrap: break-word;
.assertion-option {
cursor: pointer;
padding: 0.4rem 0.6rem;
&:hover {
background-color: #e9ecef;
}
.assertion-option-value {
font-weight: 600;
}
}
}
}
}
}
-7
View File
@@ -1,7 +0,0 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
}
export const cssExports: CssExports;
export default cssExports;
-4
View File
@@ -5,15 +5,11 @@ import { Reporter } from '@packages/reporter/src/main'
import shortcuts from '@packages/reporter/src/lib/shortcuts'
import * as MobX from 'mobx'
import { dom } from './src/dom'
export const UnifiedRunner = {
CypressJQuery: $Cypress.$,
CypressDriver: $Cypress,
dom,
shortcuts,
React,
-91
View File
@@ -1,91 +0,0 @@
diff --git a/node_modules/@popperjs/core/lib/modifiers/preventOverflow.js b/node_modules/@popperjs/core/lib/modifiers/preventOverflow.js
index 1776c09..e08aad1 100644
--- a/node_modules/@popperjs/core/lib/modifiers/preventOverflow.js
+++ b/node_modules/@popperjs/core/lib/modifiers/preventOverflow.js
@@ -52,26 +52,27 @@ function preventOverflow(_ref) {
return;
}
- if (checkMainAxis || checkAltAxis) {
- var mainSide = mainAxis === 'y' ? top : left;
- var altSide = mainAxis === 'y' ? bottom : right;
- var len = mainAxis === 'y' ? 'height' : 'width';
- var offset = popperOffsets[mainAxis];
- var min = popperOffsets[mainAxis] + overflow[mainSide];
- var max = popperOffsets[mainAxis] - overflow[altSide];
+ function setOffset(axis) {
+ var isMainAxis = axis === mainAxis;
+ var mainSide = axis === 'y' ? top : left;
+ var altSide = axis === 'y' ? bottom : right;
+ var len = axis === 'y' ? 'height' : 'width';
+ var offset = popperOffsets[axis];
+ var min = popperOffsets[axis] + overflow[mainSide];
+ var max = popperOffsets[axis] - overflow[altSide];
var additive = tether ? -popperRect[len] / 2 : 0;
var minLen = variation === start ? referenceRect[len] : popperRect[len];
var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go
- // outside the reference bounds
+ // outside the reference bounds when on the main axis
var arrowElement = state.elements.arrow;
- var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {
+ var arrowRect = tether && arrowElement && isMainAxis ? getLayoutRect(arrowElement) : {
width: 0,
height: 0
};
var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();
- var arrowPaddingMin = arrowPaddingObject[mainSide];
- var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want
+ var arrowPaddingMin = isMainAxis ? arrowPaddingObject[mainSide] : 0;
+ var arrowPaddingMax = isMainAxis ? arrowPaddingObject[altSide] : 0; // If the reference length is smaller than the arrow length, we don't want
// to include its full size in the calculation. If the reference is small
// and near the edge of a boundary, the popper can overflow even if the
// reference is not overflowing as well (e.g. virtual elements with no
@@ -81,33 +82,22 @@ function preventOverflow(_ref) {
var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - tetherOffsetValue : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue;
var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + tetherOffsetValue : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue;
var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);
- var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;
- var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][mainAxis] : 0;
- var tetherMin = popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset;
- var tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue;
-
- if (checkMainAxis) {
- var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);
- popperOffsets[mainAxis] = preventedOffset;
- data[mainAxis] = preventedOffset - offset;
- }
-
- if (checkAltAxis) {
- var _mainSide = mainAxis === 'x' ? top : left;
-
- var _altSide = mainAxis === 'x' ? bottom : right;
-
- var _offset = popperOffsets[altAxis];
-
- var _min = _offset + overflow[_mainSide];
-
- var _max = _offset - overflow[_altSide];
+ var clientOffset = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;
+ var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][axis] : 0;
+ var referenceRectEnd = referenceRect[axis] + referenceRect[len];
+ var tetherMin = !isMainAxis && referenceRect[axis] < popperRect[axis] ? popperOffsets[axis] + minOffset - offsetModifierValue - clientOffset : referenceRectEnd;
+ var tetherMax = !isMainAxis && referenceRectEnd < popperRect[axis] ? popperOffsets[axis] + maxOffset - offsetModifierValue : referenceRect[axis];
+ var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);
+ popperOffsets[axis] = preventedOffset;
+ data[axis] = preventedOffset - offset;
+ }
- var _preventedOffset = within(tether ? mathMin(_min, tetherMin) : _min, _offset, tether ? mathMax(_max, tetherMax) : _max);
+ if (checkMainAxis) {
+ setOffset(mainAxis);
+ }
- popperOffsets[altAxis] = _preventedOffset;
- data[altAxis] = _preventedOffset - _offset;
- }
+ if (checkAltAxis) {
+ setOffset(altAxis);
}
state.modifiersData[name] = data;
+5 -23
View File
@@ -5768,10 +5768,10 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.20.tgz#111b5db0f501aa89b05076fa31f0ea0e0c292cd3"
integrity sha512-88p7+M0QGxKpmnkfXjS4V26AnoC/eiqZutE8GLdaI5X12NY75bXSdTY9NkmYb2Xyk1O+MmkuO6Frmsj84V6I8Q==
"@popperjs/core@2.9.2":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
"@popperjs/core@2.11.6":
version "2.11.6"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
"@purge-icons/generated@0.8.1":
version "0.8.1"
@@ -28632,11 +28632,6 @@ react-error-overlay@^6.0.3:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
react-fast-compare@^3.0.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-focus-lock@^2.3.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.0.tgz#12e3a3940e897c26e2c2a0408cd25ea3c99b3709"
@@ -28654,14 +28649,6 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-popper@2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-refresh@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.12.0.tgz#28ac0a2c30ef2bb3433d5fd0621e69a6d774c3a4"
@@ -28763,11 +28750,6 @@ react-scripts@3.2:
optionalDependencies:
fsevents "2.0.7"
react-shadow-dom-retarget-events@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/react-shadow-dom-retarget-events/-/react-shadow-dom-retarget-events-1.0.11.tgz#e57ce3ba0bc8159fec4861778daf02161440f156"
integrity sha512-4ExKxKEWUCEmVBZmtly5lgHd9vz/NDKv5H7KmFZZxHZW/W6EmmzyOA928OqeWPxcfXUjfNG8q3hpwCD9O7CRRg==
react-style-singleton@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66"
@@ -34609,7 +34591,7 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
warning@^4.0.2, warning@^4.0.3:
warning@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==