Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions npm/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@
"css-loader": "2.1.1",
"cypress": "0.0.0-development",
"cypress-real-events": "1.1.0",
"fuse.js": "^6.4.6",
"mocha": "^7.0.1",
"react": "16.8.6",
"react-dom": "16.8.6",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rollup": "^2.38.5",
"rollup-plugin-peer-deps-external": "2.2.4",
"rollup-plugin-postcss-modules": "2.0.2",
Expand Down Expand Up @@ -93,4 +94,4 @@
"expect"
]
}
}
}
2 changes: 2 additions & 0 deletions npm/design-system/src/components/Nav/LeftNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ library.add(fas)
import styles from './LeftNav.module.scss'
import { LeftNavProps, NavButtonProps, NavLocation, NavItem } from './types'

library.add(fas)

interface NavItemDefinedLocation extends NavItem {
location: NavLocation
_index: number
Expand Down
2 changes: 2 additions & 0 deletions npm/design-system/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export * from './components/SpecList/SpecList'
export * from './components/SearchInput/SearchInput'

export * from './components/Nav'

export * from './library/hooks'
3 changes: 3 additions & 0 deletions npm/design-system/src/library/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './useCurrent'

export * from './useFuzzySearch'
16 changes: 16 additions & 0 deletions npm/design-system/src/library/hooks/useCurrent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useLayoutEffect, useRef } from 'react'

/**
* Wraps the provided `value` in a Concurrent safe ref for consumption in `useEffect` and similar without forcing reruns on value changes
*
* **CAUTION:** `useCurrent` makes it easy to shoot yourself in the foot, particularly in scenarios involving callbacks. Use sparingly
*/
export const useCurrent = <T>(value: T) => {
const ref = useRef(value)

useLayoutEffect(() => {
ref.current = value
}, [value])

return ref
}
74 changes: 74 additions & 0 deletions npm/design-system/src/library/hooks/useFuzzySearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Fuse from 'fuse.js'
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'
import * as React from 'react'

What do you think about this? Actually, this is the only right way to import React as ESM module. Moreover it is much easier to read code when default react hooks are started with React.

facebook/react#18102
https://twitter.com/sebmarkbage/status/1231673639080038400

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is the only right way to import React as ESM module

Is this actually relevant to us? Why would it matter?

much easier to read code when default react hooks are started with React.

I disagree with this and see no significant difference other than styling. I will not be changing it for your (CT) codebase, but for design-system I will continue importing React as above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did post 2 links. You are doing actually an invalid thing, you are trying to import UMD using esm default export. Once react will finally have esm export this will not be possible. So it's not the styling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know very little about the crazy mess that is different types of JS modules, but the most recent React team interaction on modules/importing was the following: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports

In particular, I will point out this:

Change all default React imports (i.e. import React from "react") to destructured named imports (ex. import { useState } from "react") which is the preferred style going into the future. This codemod will not affect the existing namespace imports (i.e. import * as React from "react") which is also a valid style. The default imports will keep working in React 17, but in the longer term we encourage moving away from them.

As a question of my own, don't import * imports hurt tree shaking?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a question of my own, don't import * imports hurt tree shaking?

No, just because for now it's still an all-piece of react in one object that just exports different parts. But yeah export default is the biggest mess.


/**
* Supports all Fuse options, with modified key definitions
*/
export interface FuzzySearchConfig<T> extends Omit<Fuse.IFuseOptions<T>, 'keys'> {
keys?: {
[Key in keyof T]?: true | {
weight: number
}
}
}

/**
* Provides fuzzy matching search over the supplied items, using weighted keys
* @param items The items to search
* @param searchInput A search string, provided if the hook is driven by external state
* @param config The configuration of how to perform searches, based on Fuse config options
*
* @see https://fusejs.io/api/options.html
*/
export const useFuzzySearch = <T>(items: T[], searchInput?: string, config?: FuzzySearchConfig<T>): {
searchInput: string
results: Fuse.FuseResult<T>[] | undefined
orderedResults: T[]
onSearch: (searchInput: string) => void
} => {
const finalConfig = useMemo((): Fuse.IFuseOptions<T> | undefined => {
if (!config?.keys) {
return config as Fuse.IFuseOptions<T>
}

return {
...config,
keys: Object.keys(config.keys).map((key) => {
const keyEntry = config.keys?.[key as keyof T] as true | {
weight: number
} | undefined

if (keyEntry === undefined) {
throw new Error('Unreachable branch. keyEntry does not exist')
}

if (typeof keyEntry === 'boolean') {
return key
}

return {
...keyEntry,
name: key,
}
}),
}
}, [config])

const [cachedFuse, setCachedFuse] = useState<Fuse<T> | undefined>(() => new Fuse(items, finalConfig))
const [lastSearchInput, setLastSearchInput] = useState<string>('')

const results = useMemo(() => lastSearchInput !== '' ? cachedFuse?.search(lastSearchInput) : undefined, [lastSearchInput, cachedFuse])
const orderedResults = useMemo(() => results?.map((r) => r.item), [results])

useLayoutEffect(() => setLastSearchInput(searchInput ?? ''), [searchInput])

useEffect(() => setCachedFuse(new Fuse(items, finalConfig)), [items, finalConfig])

return {
searchInput: lastSearchInput,
results,
// If orderedResults is undefined, that means we didn't search (empty searchInput)
orderedResults: orderedResults !== undefined ? orderedResults : items,
onSearch: setLastSearchInput,
}
}
34 changes: 34 additions & 0 deletions packages/runner-ct/cypress/component/RunnerCt.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,38 @@ describe('RunnerCt', () => {
cy.percySnapshot()
})
})

context('spec filtering', () => {
const initialState = makeState({ spec: null, specs: [
{ relative: '/test.js', absolute: 'root/test.js', name: 'test.js' },
{ relative: '/child/todo.js', absolute: 'root/child/todo.js', name: 'todo.js' },
{ relative: '/child/browser.js', absolute: 'root/child/browser.js', name: 'browser.js' },
] })

it('filters the list of items based on input', () => {
mount(<RunnerCt
state={initialState}
// @ts-ignore - this is difficult to stub. Real one breaks things.
eventManager={new FakeEventManager()}
config={fakeConfig} />)

cy.get(selectors.searchInput).type('t')
cy.get(selectors.specsList).contains('test.js').should('exist')
cy.get(selectors.specsList).contains('todo.js').should('exist')
cy.get(selectors.specsList).contains('browser.js').should('not.exist')
})

it('sufficiently narrows the input based on entire input string', () => {
mount(<RunnerCt
state={initialState}
// @ts-ignore - this is difficult to stub. Real one breaks things.
eventManager={new FakeEventManager()}
config={fakeConfig} />)

cy.get(selectors.searchInput).type('test')
cy.get(selectors.specsList).contains('test.js').should('exist')
cy.get(selectors.specsList).contains('todo.js').should('not.exist')
cy.get(selectors.specsList).contains('browser.js').should('not.exist')
})
})
})
4 changes: 3 additions & 1 deletion packages/runner-ct/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"build-prod": "cross-env NODE_ENV=production yarn build && tsc",
"clean-deps": "rm -rf node_modules",
"cypress:open": "ts-node ../../scripts/cypress.js open-ct --project ${PWD}",
"cypress:open-react": "ts-node ../../scripts/cypress.js open-ct --project ../../npm/react",
"cypress:run": "ts-node ../../scripts/cypress.js run-ct --project ${PWD}",
"postinstall": "echo '@packages/runner needs: yarn build'",
"test": "ts-node ../../scripts/cypress.js run-ct --project ${PWD}",
Expand All @@ -18,6 +19,7 @@
"@cypress/design-system": "0.0.0-development",
"@cypress/react-tooltip": "0.5.3",
"@fortawesome/free-regular-svg-icons": "5.15.2",
"@fortawesome/react-fontawesome": "0.1.14",
"ansi-to-html": "0.6.14",
"bluebird": "3.5.3",
"cash-dom": "^8.1.0",
Expand Down Expand Up @@ -68,4 +70,4 @@
"src",
"lib"
]
}
}
22 changes: 17 additions & 5 deletions packages/runner-ct/src/app/RunnerCt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'
import * as React from 'react'
import { useScreenshotHandler } from './useScreenshotHandler'
import { ReporterContainer } from './ReporterContainer'
import { NavItem, SpecList, FileNode } from '@cypress/design-system'
import { NavItem, SpecList, FileNode, useFuzzySearch, FuzzySearchConfig } from '@cypress/design-system'
import SplitPane from 'react-split-pane'

import State from '../lib/state'
Expand Down Expand Up @@ -44,6 +44,19 @@ export const AUT_IFRAME_MARGIN = {
Y: 16,
}

const fuzzyConfig: FuzzySearchConfig<Cypress.Spec> = {
keys: {
name: {
weight: 0.4,
},
relative: {
weight: 0.2,
},
},
threshold: 0.2,
ignoreLocation: true,
}

const App: React.FC<AppProps> = observer(
function App (props: AppProps) {
const searchRef = React.useRef<HTMLInputElement>(null)
Expand All @@ -53,7 +66,6 @@ const App: React.FC<AppProps> = observer(
const { state, eventManager, config } = props

const [activeIndex, setActiveIndex] = React.useState<number>(0)
const [search, setSearch] = React.useState('')
const headerRef = React.useRef(null)

const runSpec = (file: FileNode) => {
Expand Down Expand Up @@ -241,7 +253,7 @@ const App: React.FC<AppProps> = observer(
}
: {}

const filteredSpecs = props.state.specs.filter((spec) => spec.relative.toLowerCase().includes(search.toLowerCase()))
const { searchInput, orderedResults: filteredSpecs, onSearch } = useFuzzySearch(props.state.specs, undefined, fuzzyConfig)

return (
<SplitPane
Expand Down Expand Up @@ -278,8 +290,8 @@ const App: React.FC<AppProps> = observer(
searchInput={
<SearchSpec
ref={searchRef}
value={search}
onSearch={setSearch}
value={searchInput}
onSearch={debounce(onSearch)}
/>
}
/>
Expand Down
30 changes: 30 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17240,6 +17240,11 @@ functions-have-names@^1.2.1:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21"
integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==

fuse.js@^6.4.6:
version "6.4.6"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.4.6.tgz#62f216c110e5aa22486aff20be7896d19a059b79"
integrity sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw==

galactus@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/galactus/-/galactus-0.2.1.tgz#cbed2d20a40c1f5679a35908e2b9415733e78db9"
Expand Down Expand Up @@ -28724,6 +28729,15 @@ react-dom@^16.0.0:
prop-types "^15.6.2"
scheduler "^0.19.1"

react-dom@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.1"

react-error-overlay@^6.0.3, react-error-overlay@^6.0.7:
version "6.0.8"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de"
Expand Down Expand Up @@ -29121,6 +29135,14 @@ react@^16.0.0, react@^16.13.1:
object-assign "^4.1.1"
prop-types "^15.6.2"

react@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"

read-all-stream@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
Expand Down Expand Up @@ -30567,6 +30589,14 @@ scheduler@^0.19.1:
loose-envify "^1.1.0"
object-assign "^4.1.1"

scheduler@^0.20.1:
version "0.20.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"

schema-utils@2.7.1, schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.2.0, schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0, schema-utils@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
Expand Down