Skip to content

Commit

Permalink
add function "type"
Browse files Browse the repository at this point in the history
  • Loading branch information
nichoth committed Sep 27, 2024
1 parent d0d1588 commit 6c484ec
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 6 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ import { sleep } from '@bicycle-codes/dom'
await sleep(3000) // wait 3 seconds
```

### type
```ts
export async function type (
selector:string|HTMLElement|Element,
value:string,
):Promise<void>
```

#### example
```js
```

## credits

Thanks Jake Verbaten for writing this originally.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@bicycle-codes/tapzero": "^0.10.0",
"@bicycle-codes/tapzero": "^0.10.3",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"auto-changelog": "^2.4.0",
Expand Down
44 changes: 40 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toElement, requestAnimationFrame } from './util.js'
export const qs = document.querySelector.bind(document)
export const qsa = document.querySelectorAll.bind(document)

Expand Down Expand Up @@ -122,7 +123,7 @@ export function isElementVisible (
* element: Element,
* multipleTags?: boolean,
* regex?: RegExp
* }} args
* }|string} args
*/
export function waitForText (args:{
text?:string,
Expand All @@ -147,7 +148,7 @@ export function waitForText (args:{

return waitFor(
{ timeout: opts.timeout },
() => {
() => { // the lambda
const {
element,
text,
Expand Down Expand Up @@ -237,7 +238,7 @@ export function waitFor (
lambda?:Lambda
):Promise<Element|null> {
let selector:string
let visible:boolean = true
let visible:boolean
let timeout = DEFAULT_TIMEOUT
if (typeof args === 'string') {
selector = args
Expand Down Expand Up @@ -278,7 +279,7 @@ export function waitFor (
/**
* Click the given element.
*
* @param {HTMLElement} element
* @param {Element} element
*/
export function click (element:Element) {
event({
Expand Down Expand Up @@ -318,3 +319,38 @@ export function event (args:{

element.dispatchEvent(event)
}

/**
* Type the given value into the element, emitting all relevant events, to
* simulate a user typing with a keyboard.
*
* @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element.
* @param {string} value - The string to type into the :focus element.
* @returns {Promise<void>}
*
* @example
* ```js
* await type('#my-div', 'Hello World')
* ```
*/
export async function type (
selector:string|HTMLElement|Element,
value:string,
):Promise<void> {
const el = toElement(selector)

if (!('value' in el!)) throw new Error('Element missing value attribute')

for (const c of value.split('')) {
await requestAnimationFrame()
el.value = el.value != null ? el.value + c : c
el.dispatchEvent(
new Event('input', {
bubbles: true,
cancelable: true
})
)
}

await requestAnimationFrame()
}
41 changes: 41 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Converts querySelector string to an HTMLElement or validates an
* existing HTMLElement.
*
*
* @param {string|Element|HTMLElement} selector - A CSS selector string, or an
* instance of an Element.
* @returns {Element} The HTMLElement, Element, or Window that corresponds to
* the selector.
* @throws {Error} Throws an error if the `selector` is not a string that
* resolves to an HTMLElement, or not an instance of
* HTMLElement, Element, or Window.
*
*/
export function toElement (_selector:string|HTMLElement|Element) {
let selector:string|Element|null = _selector
if (globalThis.document) {
if (typeof selector === 'string') {
selector = globalThis.document.querySelector(selector)
}

if (!(
selector instanceof globalThis.HTMLElement ||
selector instanceof globalThis.Element
)) {
throw new Error('`stringOrElement` needs to be an instance of ' +
'HTMLElement or a querySelector that resolves to an HTMLElement')
}

return selector
}
}

export async function requestAnimationFrame ():Promise<void> {
if (globalThis.document && globalThis.document.hasFocus()) {
// RAF only works when the window is focused
await new Promise(resolve => globalThis.requestAnimationFrame(resolve))
} else {
await new Promise((resolve) => setTimeout(resolve, 0))
}
}
4 changes: 4 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<script src="/reporter.js"></script>
</head>
<body>
<form>
<input id="test-input" type="text" />
</form>

<script src="/test-bundle.js"></script>
</body>
</html>
13 changes: 12 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dom, waitForText } from '../src/index.js'
import { dom, qs, waitForText, type } from '../src/index.js'
import { test } from '@bicycle-codes/tapzero'
import { Terminal } from 'xterm'

Expand Down Expand Up @@ -256,3 +256,14 @@ test('waitForText', async t => {
const text = await waitForText('testing')
t.ok(text instanceof HTMLElement, 'should find the p tag given a string')
})

test('type', async t => {
t.plan(11)
const input = qs('#test-input')
t.plan(11)
input?.addEventListener('input', ev => {
t.ok(ev, 'should dispatch an "input" event 11 times, once for each key')
})

await type('#test-input', 'hello world')
})

0 comments on commit 6c484ec

Please sign in to comment.