From 033689f8bacaeb71df311be2034f76d1e776d7ea Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Fri, 18 Oct 2019 12:46:46 -0400 Subject: [PATCH] use typescript in driver (#5383) * upgrade eslint-plugin-dev to 5.0.0 * more [lint fixes] * use typescript in driver * cleanup minor * remove unneeded file * mild refactor webpack configs * fix elements.ts * fix isTextLike signature change * fix isType check, fix type_spec * actually use webpack-preprocessor * fix isType domUtil * fix connectors_spec * cleanup connectors_spec * address feedback- cleanup, add comments, refac * answer: no, need index.js --- packages/driver/package.json | 2 + packages/driver/src/config/lodash.d.ts | 43 +++ packages/driver/src/config/lodash.js | 2 + .../driver/src/cy/commands/actions/type.js | 4 +- packages/driver/src/cy/ensures.coffee | 2 +- .../src/cy/{keyboard.js => keyboard.ts} | 10 +- .../src/dom/{elements.js => elements.ts} | 253 +++++++++++------- packages/driver/src/dom/index.js | 37 +-- .../src/dom/{selection.js => selection.ts} | 18 +- .../integration/commands/actions/type_spec.js | 27 +- .../commands/connectors_spec.coffee | 12 +- packages/driver/test/cypress/plugins/index.js | 8 + packages/driver/ts/import-coffee.d.ts | 5 + packages/driver/tsconfig.json | 23 ++ packages/launcher/test/unit/detect_spec.ts | 2 +- packages/launcher/test/unit/launcher_spec.ts | 6 +- packages/launcher/tslint.json | 6 - packages/network/index.ts | 10 + packages/reporter/src/lib/shortcuts.js | 4 +- packages/runner/src/header/header.jsx | 2 +- packages/runner/ts/import-coffee.d.ts | 5 + packages/web-config/webpack.config.base.ts | 5 +- 22 files changed, 301 insertions(+), 185 deletions(-) create mode 100644 packages/driver/src/config/lodash.d.ts rename packages/driver/src/cy/{keyboard.js => keyboard.ts} (99%) rename packages/driver/src/dom/{elements.js => elements.ts} (80%) rename packages/driver/src/dom/{selection.js => selection.ts} (97%) create mode 100644 packages/driver/ts/import-coffee.d.ts create mode 100644 packages/driver/tsconfig.json delete mode 100644 packages/launcher/tslint.json create mode 100644 packages/network/index.ts create mode 100644 packages/runner/ts/import-coffee.d.ts diff --git a/packages/driver/package.json b/packages/driver/package.json index 28a79d4099..5613d09750 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -16,6 +16,7 @@ "@cypress/sinon-chai": "1.1.0", "@cypress/underscore.inflection": "1.0.1", "@cypress/unique-selector": "0.4.2", + "@cypress/webpack-preprocessor": "4.1.0", "@cypress/what-is-circular": "1.0.1", "angular": "1.7.7", "backbone": "1.4.0", @@ -60,6 +61,7 @@ "url-parse": "1.4.7", "vanilla-text-mask": "5.1.1", "wait-on": "3.3.0", + "webpack": "4.41.0", "zone.js": "0.9.0" }, "files": [ diff --git a/packages/driver/src/config/lodash.d.ts b/packages/driver/src/config/lodash.d.ts new file mode 100644 index 0000000000..b875b3040d --- /dev/null +++ b/packages/driver/src/config/lodash.d.ts @@ -0,0 +1,43 @@ +// const _ = require('lodash') +import _ from 'lodash' +import underscoreInflection from '@cypress/underscore.inflection' + +import clean from 'underscore.string/clean' +import count from 'underscore.string/count' +import isBlank from 'underscore.string/isBlank' +import toBoolean from 'underscore.string/toBoolean' +import capitalize from 'underscore.string/capitalize' + +const inflection = underscoreInflection(_) + +// only export exactly what we need, nothing more! +_.mixin({ + clean, + count, + isBlank, + toBoolean, + capitalize, // its mo' better the lodash version + ordinalize: inflection.ordinalize, +}) + +declare module 'lodash' { + export interface LoDashExplicitWrapper { + clean(...args): LoDashExplicitWrapper + count(...args): LoDashExplicitWrapper + isBlank(...args): LoDashExplicitWrapper + toBoolean(...args): LoDashExplicitWrapper + capitalize(...args): LoDashExplicitWrapper + ordinalize(...args): LoDashExplicitWrapper + } + + export interface LodashStatic { + clean(...args): LoDashExplicitWrapper + count(...args): LoDashExplicitWrapper + isBlank(...args): LoDashExplicitWrapper + toBoolean(...args): LoDashExplicitWrapper + capitalize(...args): LoDashExplicitWrapper + ordinalize(...args): LoDashExplicitWrapper + } +} + +export default _ diff --git a/packages/driver/src/config/lodash.js b/packages/driver/src/config/lodash.js index 178fca957b..4eb8010741 100644 --- a/packages/driver/src/config/lodash.js +++ b/packages/driver/src/config/lodash.js @@ -11,3 +11,5 @@ _.mixin({ capitalize: require('underscore.string/capitalize'), // its mo' better the lodash version ordinalize: inflection.ordinalize, }) + +export default _ diff --git a/packages/driver/src/cy/commands/actions/type.js b/packages/driver/src/cy/commands/actions/type.js index eb83c72937..dcff797ece 100644 --- a/packages/driver/src/cy/commands/actions/type.js +++ b/packages/driver/src/cy/commands/actions/type.js @@ -111,7 +111,7 @@ module.exports = function (Commands, Cypress, cy, state, config) { } const isBody = options.$el.is('body') - const isTextLike = $dom.isTextLike(options.$el) + const isTextLike = $dom.isTextLike(options.$el.get(0)) const isDate = $dom.isType(options.$el, 'date') const isTime = $dom.isType(options.$el, 'time') const isMonth = $dom.isType(options.$el, 'month') @@ -537,7 +537,7 @@ module.exports = function (Commands, Cypress, cy, state, config) { const node = $dom.stringify($el) - if (!$dom.isTextLike($el)) { + if (!$dom.isTextLike($el.get(0))) { const word = $utils.plural(subject, 'contains', 'is') $utils.throwErrByPath('clear.invalid_element', { diff --git a/packages/driver/src/cy/ensures.coffee b/packages/driver/src/cy/ensures.coffee index 0c16ac45bb..d901dfc7ff 100644 --- a/packages/driver/src/cy/ensures.coffee +++ b/packages/driver/src/cy/ensures.coffee @@ -117,7 +117,7 @@ create = (state, expect) -> # readonly can only be applied to input/textarea # not on checkboxes, radios, etc.. - if $dom.isTextLike(subject) and subject.prop("readonly") + if $dom.isTextLike(subject.get(0)) and subject.prop("readonly") node = $dom.stringify(subject) $utils.throwErrByPath("dom.readonly", { diff --git a/packages/driver/src/cy/keyboard.js b/packages/driver/src/cy/keyboard.ts similarity index 99% rename from packages/driver/src/cy/keyboard.js rename to packages/driver/src/cy/keyboard.ts index 109a5e8c83..dbc370aab3 100644 --- a/packages/driver/src/cy/keyboard.js +++ b/packages/driver/src/cy/keyboard.ts @@ -359,8 +359,8 @@ const create = (state) => { '{shift}': 'shift', }, - type (options = {}) { - _.defaults(options, { + type (opts = {}) { + const options = _.defaults(opts, { delay: 10, parseSpecialCharSequences: true, onEvent () {}, @@ -599,7 +599,7 @@ const create = (state) => { }) }, - ensureKey (el, key, options, fn) { + ensureKey (el, key, options, fn?: Function) { _.defaults(options, { prevText: null, }) @@ -697,7 +697,7 @@ const create = (state) => { resetModifiers (doc) { const activeEl = $elements.getActiveElByDocument(doc) - const activeModifiers = kb.getActiveModifiers(state) + const activeModifiers = kb.getActiveModifiers() for (let modifier in activeModifiers) { const isActivated = activeModifiers[modifier] @@ -718,7 +718,7 @@ const create = (state) => { return kb } -module.exports = { +export { create, toModifiersEventOptions, modifiersToString, diff --git a/packages/driver/src/dom/elements.js b/packages/driver/src/dom/elements.ts similarity index 80% rename from packages/driver/src/dom/elements.js rename to packages/driver/src/dom/elements.ts index 7b217529f6..fa1ced27bf 100644 --- a/packages/driver/src/dom/elements.js +++ b/packages/driver/src/dom/elements.ts @@ -1,17 +1,18 @@ -const _ = require('lodash') -const $ = require('jquery') -const $jquery = require('./jquery') -const $window = require('./window') -const $document = require('./document') -const $utils = require('../cypress/utils') -const $selection = require('./selection') -const { parentHasDisplayNone } = require('./visibility') +// NOT patched jquery +import $ from 'jquery' +import * as $jquery from './jquery' +import * as $window from './window' +import * as $document from './document' +import $utils from '../cypress/utils.coffee' +import * as $selection from './selection' +import _ from '../config/lodash' +import { parentHasDisplayNone } from './visibility' const { wrap } = $jquery const fixedOrStickyRe = /(fixed|sticky)/ -const focusable = [ +const focusableSelectors = [ 'a[href]', 'area[href]', 'input:not([disabled])', @@ -20,17 +21,43 @@ const focusable = [ 'button:not([disabled])', 'iframe', '[tabindex]', - '[contentEditable]', + '[contenteditable]', ] const inputTypeNeedSingleValueChangeRe = /^(date|time|month|week)$/ const canSetSelectionRangeElementRe = /^(text|search|URL|tel|password)$/ +declare global { + interface Window { + Element: typeof Element + HTMLElement: typeof HTMLElement + HTMLInputElement: typeof HTMLInputElement + HTMLSelectElement: typeof HTMLSelectElement + HTMLButtonElement: typeof HTMLButtonElement + HTMLOptionElement: typeof HTMLOptionElement + HTMLTextAreaElement: typeof HTMLTextAreaElement + Selection: typeof Selection + SVGElement: typeof SVGElement + EventTarget: typeof EventTarget + Document: typeof Document + } + + interface Selection { + modify: Function + } +} + // rules for native methods and props // if a setter or getter or function then add a native method // if a traversal, don't -const descriptor = (klass, prop) => { - return Object.getOwnPropertyDescriptor(window[klass].prototype, prop) +const descriptor = (klass: T, prop: K) => { + const descriptor = Object.getOwnPropertyDescriptor(window[klass].prototype, prop) + + if (descriptor === undefined) { + throw new Error(`Error, could not get property descriptor for ${klass} ${prop}. This should never happen`) + } + + return descriptor } const _getValue = function () { @@ -83,6 +110,8 @@ const _getSelectionStart = function () { if (isTextarea(this)) { return descriptor('HTMLTextAreaElement', 'selectionStart').get } + + throw new Error('this should never happen, cannot get selectionStart') } const _getSelectionEnd = function () { @@ -93,6 +122,8 @@ const _getSelectionEnd = function () { if (isTextarea(this)) { return descriptor('HTMLTextAreaElement', 'selectionEnd').get } + + throw new Error('this should never happen, cannot get selectionEnd') } const _nativeFocus = function () { @@ -153,6 +184,8 @@ const _setType = function () { if (isButton(this)) { return descriptor('HTMLButtonElement', 'type').set } + + throw new Error('this should never happen, cannot set type') } const _getType = function () { @@ -163,6 +196,8 @@ const _getType = function () { if (isButton(this)) { return descriptor('HTMLButtonElement', 'type').get } + + throw new Error('this should never happen, cannot get type') } const nativeGetters = { @@ -174,7 +209,7 @@ const nativeGetters = { type: _getType, activeElement: descriptor('Document', 'activeElement').get, body: descriptor('Document', 'body').get, - frameElement: Object.getOwnPropertyDescriptor(window, 'frameElement').get, + frameElement: Object.getOwnPropertyDescriptor(window, 'frameElement')!.get, } const nativeSetters = { @@ -199,9 +234,9 @@ const nativeMethods = { select: _nativeSelect, } -const tryCallNativeMethod = (...args) => { +const tryCallNativeMethod = (obj, fn, ...args) => { try { - return callNativeMethod(...args) + return callNativeMethod(obj, fn, ...args) } catch (err) { return } @@ -224,9 +259,8 @@ const callNativeMethod = function (obj, fn, ...args) { return retFn } - -const getNativeProp = function (obj, prop) { - const nativeProp = nativeGetters[prop] +const getNativeProp = function (obj: T, prop: K): T[K] { + const nativeProp = nativeGetters[prop as string] if (!nativeProp) { const props = _.keys(nativeGetters).join(', ') @@ -245,8 +279,8 @@ const getNativeProp = function (obj, prop) { return retProp } -const setNativeProp = function (obj, prop, val) { - const nativeProp = nativeSetters[prop] +const setNativeProp = function (obj: T, prop: K, val) { + const nativeProp = nativeSetters[prop as string] if (!nativeProp) { const fns = _.keys(nativeSetters).join(', ') @@ -263,7 +297,11 @@ const setNativeProp = function (obj, prop, val) { return retProp } -const isNeedSingleValueChangeInputElement = (el) => { +export interface HTMLSingleValueChangeInputElement extends HTMLInputElement { + type: 'date' | 'time' | 'week' | 'month' +} + +const isNeedSingleValueChangeInputElement = (el: HTMLElement): el is HTMLSingleValueChangeInputElement => { if (!isInput(el)) { return false } @@ -271,7 +309,8 @@ const isNeedSingleValueChangeInputElement = (el) => { return inputTypeNeedSingleValueChangeRe.test(el.type) } -const canSetSelectionRangeElement = (el) => { +const canSetSelectionRangeElement = (el): el is HTMLElementCanSetSelectionRange => { + //TODO: If IE, all inputs can set selection range return isTextarea(el) || (isInput(el) && canSetSelectionRangeElementRe.test(getNativeProp(el, 'type'))) } @@ -285,23 +324,23 @@ const getTagName = (el) => { // should be true for elements: // - with [contenteditable] // - with document.designMode = 'on' -const isContentEditable = (el) => { +const isContentEditable = (el: any): el is HTMLContentEditableElement => { return getNativeProp(el, 'isContentEditable') } -const isTextarea = (el) => { +const isTextarea = (el): el is HTMLTextAreaElement => { return getTagName(el) === 'textarea' } -const isInput = (el) => { +const isInput = (el): el is HTMLInputElement => { return getTagName(el) === 'input' } -const isButton = (el) => { +const isButton = (el): el is HTMLButtonElement => { return getTagName(el) === 'button' } -const isSelect = (el) => { +const isSelect = (el): el is HTMLSelectElement => { return getTagName(el) === 'select' } @@ -313,7 +352,7 @@ const isOptgroup = (el) => { return getTagName(el) === 'optgroup' } -const isBody = (el) => { +const isBody = (el): el is HTMLBodyElement => { return getTagName(el) === 'body' } @@ -325,7 +364,7 @@ const isHTML = (el) => { return getTagName(el) === 'html' } -const isSvg = function (el) { +const isSvg = function (el): el is SVGElement { try { return 'ownerSVGElement' in el } catch (error) { @@ -336,7 +375,7 @@ const isSvg = function (el) { // active element is the default if its null // or its equal to document.body const activeElementIsDefault = (activeElement, body) => { - return (!activeElement) || (activeElement === body) + return !activeElement || activeElement === body } const isFocused = (el) => { @@ -375,9 +414,11 @@ const isFocusedOrInFocused = (el) => { if (elToCheckCurrentlyFocused && elToCheckCurrentlyFocused === activeElement) { return true } + + return false } -const isElement = function (obj) { +const isElement = function (obj): obj is HTMLElement | JQuery { try { if ($jquery.isJquery(obj)) { obj = obj[0] @@ -389,10 +430,24 @@ const isElement = function (obj) { } } -const isFocusable = ($el) => { - return _.some(focusable, (sel) => { - return $el.is(sel) - }) +/** + * The element can be activeElement, receive focus events, and also receive keyboard events + */ +const isFocusable = ($el: JQuery) => { + // matches a focusable selector + if (_.some(focusableSelectors, (sel) => $el.is(sel))) { + return true + } + + // when document.designMode === 'on' (indicated by truthy isContentEditable) + // the documentElement will be focusable + if ( + (isElement($el[0]) && getTagName($el[0]) === 'html' && isContentEditable($el[0])) + ) { + return true + } + + return false } const isW3CRendered = (el) => { @@ -405,8 +460,15 @@ const isW3CFocusable = (el) => { return isFocusable(wrap(el)) && isW3CRendered(el) } -const isType = function ($el, type) { - const el = [].concat($jquery.unwrap($el))[0] +type JQueryOrEl = JQuery | T + +const isType = function (el: JQueryOrEl, type) { + el = ([] as HTMLElement[]).concat($jquery.unwrap(el))[0] + + if (!isInput(el) && !isButton(el)) { + return false + } + // NOTE: use DOMElement.type instead of getAttribute('type') since // will have type="text", and behaves like text type const elType = (getNativeProp(el, 'type') || '').toLowerCase() @@ -419,7 +481,7 @@ const isType = function ($el, type) { } const isScrollOrAuto = (prop) => { - return (prop === 'scroll') || (prop === 'auto') + return prop === 'scroll' || prop === 'auto' } const isAncestor = ($el, $maybeAncestor) => { @@ -443,7 +505,7 @@ const getFirstCommonAncestor = (el1, el2) => { const getAllParents = (el) => { let curEl = el.parentNode - const allParents = [] + const allParents: any[] = [] while (curEl) { allParents.push(curEl) @@ -457,7 +519,7 @@ const isChild = ($el, $maybeChild) => { return $el.children().index($maybeChild) >= 0 } -const isSelector = ($el, selector) => { +const isSelector = ($el: JQuery, selector) => { return $el.is(selector) } @@ -498,7 +560,7 @@ const isAttached = function ($el) { // is technically bound to a differnet document // but c'mon const isIn = (el) => { - return $.contains(doc, el) + return $.contains((doc as unknown) as Element, el) } // make sure the document is currently @@ -529,15 +591,53 @@ const isSame = function ($el1, $el2) { return el1 && el2 && _.isEqual(el1, el2) } -const isTextLike = function ($el) { +export interface HTMLContentEditableElement extends HTMLElement { + isContenteditable: true +} + +export interface HTMLTextLikeInputElement extends HTMLInputElement { + type: + | 'text' + | 'password' + | 'email' + | 'number' + | 'date' + | 'week' + | 'month' + | 'time' + | 'datetime' + | 'datetime-local' + | 'search' + | 'url' + | 'tel' + setSelectionRange: HTMLInputElement['setSelectionRange'] +} + +export interface HTMLElementCanSetSelectionRange extends HTMLElement { + setSelectionRange: HTMLInputElement['setSelectionRange'] + value: HTMLInputElement['value'] + selectionStart: number + selectionEnd: number +} + +export type HTMLTextLikeElement = HTMLTextAreaElement | HTMLTextLikeInputElement | HTMLContentEditableElement + +const isTextLike = function (el: HTMLElement): el is HTMLTextLikeElement { + const $el = $jquery.wrap(el) const sel = (selector) => { return isSelector($el, selector) } const type = (type) => { - return isType($el, type) + if (isInput(el)) { + return isType(el, type) + } + + return false } - const isContentEditableElement = isContentEditable($el.get(0)) + const isContentEditableElement = isContentEditable(el) + + if (isContentEditableElement) return true return _.some([ isContentEditableElement, @@ -636,7 +736,7 @@ const isDescendent = ($el1, $el2) => { return false } - return !!(($el1.get(0) === $el2.get(0)) || $el1.has($el2).length) + return !!($el1.get(0) === $el2.get(0) || $el1.has($el2).length) } const findParent = (el, fn) => { @@ -664,7 +764,7 @@ const findParent = (el, fn) => { // 2. check to figure out the element listed at those coordinates // 3. if this element is ourself or our descendants, click whatever was returned // 4. else throw an error because something is covering us up -const getFirstFocusableEl = ($el) => { +const getFirstFocusableEl = ($el: JQuery) => { if (isFocusable($el)) { return $el } @@ -856,8 +956,15 @@ const stringify = (el, form = 'long') => { const $el = $jquery.wrap(el) const long = () => { - const str = $el.clone().empty().prop('outerHTML') - const text = _.chain($el.text()).clean().truncate({ length: 10 }).value() + const str = $el + .clone() + .empty() + .prop('outerHTML') + + const text = (_.chain($el.text()) as any) + .clean() + .truncate({ length: 10 }) + .value() const children = $el.children().length if (children) { @@ -903,96 +1010,50 @@ const stringify = (el, form = 'long') => { }) } -// We extend `module.exports` to allow circular dependencies using `require` -// Otherwise we would not be able to `require` this util from `./selection`, for example. -_.extend(module.exports, { +export { isElement, - isSelector, - isScrollOrAuto, - isFocusable, - isW3CFocusable, - isAttached, - isDetached, - isAttachedEl, - isDetachedEl, - isAncestor, - isChild, - isScrollable, - isTextLike, - isDescendent, - isContentEditable, - isSame, - isOption, - isOptgroup, - isBody, - isHTML, - isInput, - isIframe, - isTextarea, - isType, - isFocused, - isFocusedOrInFocused, - isInputAllowingImplicitFormSubmission, - isNeedSingleValueChangeInputElement, - canSetSelectionRangeElement, - stringify, - getNativeProp, - setNativeProp, - callNativeMethod, - tryCallNativeMethod, - findParent, - getElements, - getFirstFocusableEl, - getActiveElByDocument, - getContainsSelector, - getFirstDeepestElement, - getFirstCommonAncestor, - getFirstParentWithTagName, - getFirstFixedOrStickyPositionParent, - getFirstStickyPositionParent, - getFirstScrollableParent, -}) +} diff --git a/packages/driver/src/dom/index.js b/packages/driver/src/dom/index.js index 6b4fab4d34..307317c7e8 100644 --- a/packages/driver/src/dom/index.js +++ b/packages/driver/src/dom/index.js @@ -11,7 +11,7 @@ const { wrap, unwrap, isJquery, query } = $jquery const { isVisible, isHidden, getReasonIsHidden } = $visibility const { isType, isFocusable, isElement, isScrollable, isFocused, stringify, getElements, getContainsSelector, getFirstDeepestElement, isDetached, isAttached, isTextLike, isSelector, isDescendent, getFirstFixedOrStickyPositionParent, getFirstStickyPositionParent, getFirstScrollableParent } = $elements const { getCoordsByPosition, getElementPositioning, getElementCoordinatesByPosition, getElementAtPointFromViewport, getElementCoordinatesByPositionRelativeToXY } = $coordinates - +const { getHostContenteditable, getSelectionBounds } = require('./selection') const isDom = (obj) => { return isElement(obj) || isWindow(obj) || isDocument(obj) } @@ -23,69 +23,38 @@ const isDom = (obj) => { // can be tucked away behind these interfaces. module.exports = { wrap, - query, - unwrap, - isDom, - isType, - isVisible, - isHidden, - isFocusable, - isTextLike, - isScrollable, - isFocused, - isDetached, - isAttached, - isSelector, - isDescendent, - isElement, - isDocument, - isWindow, - isJquery, - stringify, - getElements, - getContainsSelector, - getFirstDeepestElement, - getWindowByElement, - getReasonIsHidden, - getFirstScrollableParent, - getFirstFixedOrStickyPositionParent, - getFirstStickyPositionParent, - getCoordsByPosition, - getElementPositioning, - getElementAtPointFromViewport, - getElementCoordinatesByPosition, - getElementCoordinatesByPositionRelativeToXY, - + getHostContenteditable, + getSelectionBounds, } diff --git a/packages/driver/src/dom/selection.js b/packages/driver/src/dom/selection.ts similarity index 97% rename from packages/driver/src/dom/selection.js rename to packages/driver/src/dom/selection.ts index 84d1b674fa..c016872457 100644 --- a/packages/driver/src/dom/selection.js +++ b/packages/driver/src/dom/selection.ts @@ -1,13 +1,5 @@ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const $document = require('./document') -const $elements = require('./elements') +import * as $document from './document' +import * as $elements from './elements' const INTERNAL_STATE = '__Cypress_state__' @@ -222,6 +214,8 @@ const deleteRightOfCursor = function (el) { //# successful delete return true } + + return false } const deleteLeftOfCursor = function (el) { @@ -257,6 +251,8 @@ const deleteLeftOfCursor = function (el) { //# successful delete return true } + + return false } const _collapseInputOrTextArea = (el, toIndex) => { @@ -587,7 +583,7 @@ const interceptSelect = function () { // el = el.firstChild // return el -module.exports = { +export { getSelectionBounds, deleteRightOfCursor, deleteLeftOfCursor, diff --git a/packages/driver/test/cypress/integration/commands/actions/type_spec.js b/packages/driver/test/cypress/integration/commands/actions/type_spec.js index ef2eab1680..500188d757 100644 --- a/packages/driver/test/cypress/integration/commands/actions/type_spec.js +++ b/packages/driver/test/cypress/integration/commands/actions/type_spec.js @@ -1,7 +1,6 @@ const $ = Cypress.$.bind(Cypress) const { _ } = Cypress const { Promise } = Cypress -const $selection = require('../../../../../src/dom/selection') const { getCommandLogWithText, findReactInstance, withMutableReporterState } = require('../../../support/utils') // trim new lines at the end of innerText @@ -3209,7 +3208,7 @@ describe('src/cy/commands/actions/type', () => { const hostEl = cy.$$('
foo
').appendTo(cy.$$('body')) cy.get('#ce-inner1').then(($el) => { - expect($selection.getHostContenteditable($el[0])).to.eq(hostEl[0]) + expect(Cypress.dom.getHostContenteditable($el[0])).to.eq(hostEl[0]) }) }) @@ -3217,7 +3216,7 @@ describe('src/cy/commands/actions/type', () => { const hostEl = cy.$$('
foo
').appendTo(cy.$$('body')) cy.get('#ce-inner1').then(($el) => { - expect($selection.getHostContenteditable($el[0])).to.eq(hostEl[0]) + expect(Cypress.dom.getHostContenteditable($el[0])).to.eq(hostEl[0]) }) }) @@ -3225,7 +3224,7 @@ describe('src/cy/commands/actions/type', () => { const hostEl = cy.$$('
foo
').appendTo(cy.$$('body')) cy.get('#ce-inner1').then(($el) => { - expect($selection.getHostContenteditable($el[0])).to.eq(hostEl[0]) + expect(Cypress.dom.getHostContenteditable($el[0])).to.eq(hostEl[0]) }) }) @@ -3233,7 +3232,7 @@ describe('src/cy/commands/actions/type', () => { const hostEl = cy.$$('
foo
').appendTo(cy.$$('body')) cy.get('#ce-inner1').then(($el) => { - expect($selection.getHostContenteditable($el[0])).to.eq(hostEl[0]) + expect(Cypress.dom.getHostContenteditable($el[0])).to.eq(hostEl[0]) }) }) @@ -3241,7 +3240,7 @@ describe('src/cy/commands/actions/type', () => { cy.$$('
foo
').appendTo(cy.$$('body')) cy.get('#ce-inner1').then(($el) => { - expect($selection.getHostContenteditable($el[0])).to.eq($el[0]) + expect(Cypress.dom.getHostContenteditable($el[0])).to.eq($el[0]) }) }) @@ -3300,7 +3299,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('input:first').invoke('attr', 'maxlength', '5').type('foobar{leftarrow}') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('input:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('input:first').get(0))) .to.deep.eq({ start: 4, end: 4 }) }) }) @@ -3309,7 +3308,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('input:first').type('foo{rightarrow}{rightarrow}{rightarrow}bar{rightarrow}') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('input:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('input:first').get(0))) .to.deep.eq({ start: 6, end: 6 }) }) }) @@ -3318,7 +3317,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('input:first').type(`oo{leftarrow}{leftarrow}{leftarrow}f${'{leftarrow}'.repeat(5)}`) cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('input:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('input:first').get(0))) .to.deep.eq({ start: 0, end: 0 }) }) }) @@ -3327,7 +3326,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('[contenteditable]:first').type('foobar') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('[contenteditable]:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('[contenteditable]:first').get(0))) .to.deep.eq({ start: 6, end: 6 }) }) }) @@ -3340,7 +3339,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('[contenteditable]:first').type('bar') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('[contenteditable]:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('[contenteditable]:first').get(0))) .to.deep.eq({ start: 6, end: 6 }) }) }) @@ -3349,7 +3348,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('[contenteditable]:first').type('foo{leftarrow}{leftarrow}') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('[contenteditable]:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('[contenteditable]:first').get(0))) .to.deep.eq({ start: 1, end: 1 }) }) }) @@ -3364,7 +3363,7 @@ describe('src/cy/commands/actions/type', () => { cy.get(':text:first').type('foobar') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$(':text:first').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$(':text:first').get(0))) .to.deep.eq({ start: 6, end: 6 }) }) }) @@ -3373,7 +3372,7 @@ describe('src/cy/commands/actions/type', () => { cy.get('#comments').type('foobar') cy.window().then((win) => { - expect($selection.getSelectionBounds(Cypress.$('#comments').get(0))) + expect(Cypress.dom.getSelectionBounds(Cypress.$('#comments').get(0))) .to.deep.eq({ start: 6, end: 6 }) }) }) diff --git a/packages/driver/test/cypress/integration/commands/connectors_spec.coffee b/packages/driver/test/cypress/integration/commands/connectors_spec.coffee index c1547b34c3..f4f03da039 100644 --- a/packages/driver/test/cypress/integration/commands/connectors_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/connectors_spec.coffee @@ -24,9 +24,9 @@ describe "src/cy/commands/connectors", -> it "spreads a jQuery wrapper into individual arguments", -> cy.noop($("div")).spread (first, second) -> - expect(first.tagName).to.eq('DIV'); + expect(first.tagName).to.eq('DIV') expect(first.innerText).to.eq("div") - expect(second.tagName).to.eq('DIV'); + expect(second.tagName).to.eq('DIV') expect(second.innerText).to.contain("Nested Find") it "passes timeout option to spread", -> @@ -480,8 +480,8 @@ describe "src/cy/commands/connectors", -> memo + num , 0 math: { - sum: => - @obj.sum.apply(@obj, arguments) + sum: (args...) => + @obj.sum.apply(@obj, args) } } @@ -1222,7 +1222,7 @@ describe "src/cy/commands/connectors", -> it "awaits promises returned", -> count = 0 - start = new Date + start = new Date() cy.get("#list li").each ($li, i, arr) -> new Promise (resolve, reject) -> @@ -1232,7 +1232,7 @@ describe "src/cy/commands/connectors", -> , 20 .then ($lis) -> expect(count).to.eq(3) - expect(new Date - start).to.be.gt(60) + expect(new Date() - start).to.be.gt(60) it "supports array like structures", -> count = 0 diff --git a/packages/driver/test/cypress/plugins/index.js b/packages/driver/test/cypress/plugins/index.js index 317be19c1e..91a6540921 100644 --- a/packages/driver/test/cypress/plugins/index.js +++ b/packages/driver/test/cypress/plugins/index.js @@ -1,9 +1,17 @@ +// only required to read in webpack config, since it is .ts +require('@packages/ts/register') + const _ = require('lodash') const path = require('path') const fs = require('fs-extra') const Promise = require('bluebird') +const webpack = require('@cypress/webpack-preprocessor') + +const webpackOptions = require('@packages/runner/webpack.config.ts').default module.exports = (on) => { + on('file:preprocessor', webpack({ webpackOptions })) + on('task', { 'return:arg' (arg) { return arg diff --git a/packages/driver/ts/import-coffee.d.ts b/packages/driver/ts/import-coffee.d.ts new file mode 100644 index 0000000000..afd1157db4 --- /dev/null +++ b/packages/driver/ts/import-coffee.d.ts @@ -0,0 +1,5 @@ +// allows Typescript to import .coffee files +declare module '*.coffee' { + const content: any + export default content +} diff --git a/packages/driver/tsconfig.json b/packages/driver/tsconfig.json new file mode 100644 index 0000000000..9255340ab9 --- /dev/null +++ b/packages/driver/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "allowJs": true, + "noImplicitAny": false, + "noImplicitThis": false, + "strictFunctionTypes": true, + "preserveWatchOutput": true, + "sourceMap": true, + "importHelpers": true, + "strictNullChecks": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "noEmit": true, + "noImplicitReturns": true, + "allowSyntheticDefaultImports": true, + "outDir": "dist", + "esModuleInterop": true, + "noErrorTruncation": true + } +} diff --git a/packages/launcher/test/unit/detect_spec.ts b/packages/launcher/test/unit/detect_spec.ts index a5ac4fbef5..dc3fc0d397 100644 --- a/packages/launcher/test/unit/detect_spec.ts +++ b/packages/launcher/test/unit/detect_spec.ts @@ -12,7 +12,7 @@ describe('browser detection', () => { // using DEBUG=... flag const checkBrowsers = (browsers) => { log('detected browsers %j', browsers) - expect(browsers).to.be.an.array + expect(browsers).to.be.an('array') const mainProps = project(['name', 'version'], browsers) diff --git a/packages/launcher/test/unit/launcher_spec.ts b/packages/launcher/test/unit/launcher_spec.ts index 57a10a3547..24718bb89a 100644 --- a/packages/launcher/test/unit/launcher_spec.ts +++ b/packages/launcher/test/unit/launcher_spec.ts @@ -4,8 +4,8 @@ const api = require('../..') describe('launcher', function () { it('has all needed methods', function () { - expect(api.launch).to.be.a.function - expect(api.detect).to.be.a.function - expect(api.detectByPath).to.be.a.function + expect(api.launch).to.be.a('function') + expect(api.detect).to.be.a('function') + expect(api.detectByPath).to.be.a('function') }) }) diff --git a/packages/launcher/tslint.json b/packages/launcher/tslint.json deleted file mode 100644 index 83b3cfb94e..0000000000 --- a/packages/launcher/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "tslint-config-standard", - "rules": { - "space-before-function-paren": false - } -} diff --git a/packages/network/index.ts b/packages/network/index.ts new file mode 100644 index 0000000000..9e0c8d78ee --- /dev/null +++ b/packages/network/index.ts @@ -0,0 +1,10 @@ +if (process.env.CYPRESS_ENV !== 'production') { + require('@packages/ts/register') +} + +import agent from './lib/agent' +import * as connect from './lib/connect' + +export { agent } + +export { connect } diff --git a/packages/reporter/src/lib/shortcuts.js b/packages/reporter/src/lib/shortcuts.js index 7fba35691c..06b1d8d498 100644 --- a/packages/reporter/src/lib/shortcuts.js +++ b/packages/reporter/src/lib/shortcuts.js @@ -1,4 +1,4 @@ -import { $, dom } from '@packages/driver' +import { dom } from '@packages/driver' import events from './events' class Shortcuts { @@ -12,7 +12,7 @@ class Shortcuts { _handleKeyDownEvent (event) { // if typing into an input, textarea, etc, don't trigger any shortcuts - if (dom.isTextLike($(event.target))) return + if (dom.isTextLike(event.target)) return switch (event.key) { case 'r': events.emit('restart') diff --git a/packages/runner/src/header/header.jsx b/packages/runner/src/header/header.jsx index dc539b9107..50f5e191f4 100644 --- a/packages/runner/src/header/header.jsx +++ b/packages/runner/src/header/header.jsx @@ -60,7 +60,7 @@ export default class Header extends Component {

The viewport determines the width and height of your application. By default the viewport will be {state.defaults.width}px by {state.defaults.height}px unless specified by a cy.viewport command.

Additionally you can override the default viewport dimensions by specifying these values in your {configFileFormatted(config.configFile)}.

{/* eslint-disable indent */}
-{`{
+                {`{
   "viewportWidth": ${state.defaults.width},
   "viewportHeight": ${state.defaults.height}
 }`}
diff --git a/packages/runner/ts/import-coffee.d.ts b/packages/runner/ts/import-coffee.d.ts
new file mode 100644
index 0000000000..afd1157db4
--- /dev/null
+++ b/packages/runner/ts/import-coffee.d.ts
@@ -0,0 +1,5 @@
+// allows Typescript to import .coffee files
+declare module '*.coffee' {
+  const content: any
+  export default content
+}
diff --git a/packages/web-config/webpack.config.base.ts b/packages/web-config/webpack.config.base.ts
index 75781733bb..be73c77268 100644
--- a/packages/web-config/webpack.config.base.ts
+++ b/packages/web-config/webpack.config.base.ts
@@ -22,7 +22,7 @@ if (liveReloadEnabled && watchModeEnabled) console.log(chalk.gray(`\nLive Reload
 
 process.env.NODE_ENV = env
 
-const config: webpack.Configuration = {
+const commonConfig: webpack.Configuration = {
   mode: 'none',
   node: {
     fs: 'empty',
@@ -191,9 +191,8 @@ const config: webpack.Configuration = {
   ],
 
   cache: true,
-
 }
 
-export default config
+export default commonConfig
 
 export { HtmlWebpackPlugin }