Add useHighlight hook

This commit is contained in:
Tom Southall
2022-02-17 21:14:56 +00:00
parent fc76372086
commit 3488c2951c
3 changed files with 114 additions and 26 deletions

View File

@@ -12,6 +12,7 @@ import {
useItemsState, useItemsState,
useAutoFocus, useAutoFocus,
useQueryChange, useQueryChange,
useHighlight
} from './hooks/containerEffects' } from './hooks/containerEffects'
import { import {
setQuery, setQuery,
@@ -95,25 +96,12 @@ export default function Container(props) {
useAutoFocus(queryInput, autoFocus) useAutoFocus(queryInput, autoFocus)
// As soon as the query state changes (ignoring debounce) update the // As soon as the query state changes (ignoring debounce) update the
// typeahead value and the query value and fire onChnage // typeahead value and the query value and fire onChange
useQueryChange(state.query, queryInput, typeaheadInput, onChange) useQueryChange(state.query, queryInput, typeaheadInput, onChange)
// When the highlighted item changes, make sure the typeahead matches and format // When the highlighted item changes, make sure the typeahead matches and format
// the query text to match the case of the typeahead text // the query text to match the case of the typeahead text
useEffect(() => { useHighlight(state.highlighted, hasFocus, queryInput, typeaheadInput)
const typeAheadValue =
state.highlighted &&
hasFocus &&
queryInput.current.value.length > 0 &&
startsWithCaseInsensitive(state.highlighted.text, queryInput.current.value)
? state.highlighted.text
: ''
const queryValue = formatQuery(queryInput.current.value, typeAheadValue)
typeaheadInput.current.value = typeAheadValue
setify(queryInput.current, queryValue)
}, [state.highlighted, hasFocus])
// When an item is selected alter the query to match and fire applicable events // When an item is selected alter the query to match and fire applicable events
useEffect(() => { useEffect(() => {
@@ -125,15 +113,6 @@ export default function Container(props) {
} }
}, [state.selected, onSelect]) }, [state.selected, onSelect])
const formatQuery = (query, typeahead) => {
const formattedQuery = typeahead.substring(0, query.length)
return formattedQuery.length > 0 &&
query.toLowerCase() === formattedQuery.toLowerCase() &&
query !== formattedQuery
? formattedQuery
: query
}
const onTabOrEnter = (keyPressed) => { const onTabOrEnter = (keyPressed) => {
// keyPressed must be 'enter' or 'tab' // keyPressed must be 'enter' or 'tab'
const highlightedIndex = state.highlighted && state.highlighted.index const highlightedIndex = state.highlighted && state.highlighted.index

View File

@@ -2,6 +2,7 @@ import { useEffect, useContext } from 'react'
import setify from 'setify' // Sets input value without changing cursor position import setify from 'setify' // Sets input value without changing cursor position
import { StateContext } from '../../context/state' import { StateContext } from '../../context/state'
import { setItems } from '../../actions/actions' import { setItems } from '../../actions/actions'
import startsWithCaseInsensitive from '../../utils/startsWithCaseInsensitive'
export const useItemsState = (swrData) => { export const useItemsState = (swrData) => {
const { dispatch } = useContext(StateContext) const { dispatch } = useContext(StateContext)
@@ -23,7 +24,6 @@ export const useQueryChange = (query, queryInput, typeaheadInput, onChange) => {
const value = (() => { const value = (() => {
const currentValue = typeaheadInput.current.value const currentValue = typeaheadInput.current.value
if (!query) return '' if (!query) return ''
//if (!queryMatchesTypeahead(query, currentValue, true)) return ''
if (!currentValue.startsWith(query)) return '' if (!currentValue.startsWith(query)) return ''
return currentValue return currentValue
})() })()
@@ -33,4 +33,34 @@ export const useQueryChange = (query, queryInput, typeaheadInput, onChange) => {
setify(queryInput.current, query) setify(queryInput.current, query)
if (typeof onChange === 'function') onChange(query) if (typeof onChange === 'function') onChange(query)
}, [query, onChange]) }, [query, onChange])
}
export const useHighlight = (highlighted, hasFocus, queryInput, typeaheadInput) => {
useEffect(() => {
const typeAheadValue =
highlighted &&
hasFocus &&
queryInput.current.value.length > 0 &&
startsWithCaseInsensitive(highlighted.text, queryInput.current.value)
? highlighted.text
: ''
const queryValue = formatQuery(queryInput.current.value, typeAheadValue)
typeaheadInput.current.value = typeAheadValue
setify(queryInput.current, queryValue)
}, [highlighted, hasFocus])
}
//////////////////////////////
// Helper functions //
//////////////////////////////
export const formatQuery = (query, typeahead) => {
const formattedQuery = typeahead.substring(0, query.length)
return formattedQuery.length > 0 &&
query.toLowerCase() === formattedQuery.toLowerCase() &&
query !== formattedQuery
? formattedQuery
: query
} }

View File

@@ -1,8 +1,11 @@
import { vi, describe, test, expect } from 'vitest' import { vi, describe, test, expect } from 'vitest'
import { renderHook } from '@testing-library/react-hooks' import { renderHook } from '@testing-library/react-hooks'
import undef from '../../utils/undef'
import { import {
useAutoFocus, useAutoFocus,
useQueryChange useQueryChange,
useHighlight,
formatQuery
} from './containerEffects' } from './containerEffects'
let inputRef = (value = '') => ( //TODO: Put in a beforeEach when blogging let inputRef = (value = '') => ( //TODO: Put in a beforeEach when blogging
@@ -73,4 +76,80 @@ describe('useQueryChange', () => {
renderHook(() => useQueryChange(queryValue, queryRef, typeaheadRef, onChange)) renderHook(() => useQueryChange(queryValue, queryRef, typeaheadRef, onChange))
expect(onChange).toHaveBeenCalledWith(queryValue) expect(onChange).toHaveBeenCalledWith(queryValue)
}) })
})
describe('useHighlight', () => {
const queryValue = 'chi'
const highlighted = {index: 0, text: 'Chicago, Illinois, United States'}
test('Typeahead is set to highlighted text and query text changes to match case', () => {
const queryRef = inputRef(queryValue)
const typeaheadRef = inputRef()
renderHook(() => useHighlight(highlighted, true, queryRef, typeaheadRef))
expect(queryRef.current.value).toBe('Chi')
expect(typeaheadRef.current.value).toBe(highlighted.text)
})
test('If there is no highlighted item, no change occurs', () => {
const queryRef = inputRef(queryValue)
const typeaheadRef = inputRef()
renderHook(() => useHighlight(undef, true, queryRef, typeaheadRef))
expect(queryRef.current.value).toBe(queryValue)
expect(typeaheadRef.current.value).toBe('')
})
test('If focus is not set, no change occurs', () => {
const queryRef = inputRef(queryValue)
const typeaheadRef = inputRef()
renderHook(() => useHighlight(highlighted, false, queryRef, typeaheadRef))
expect(queryRef.current.value).toBe(queryValue)
expect(typeaheadRef.current.value).toBe('')
})
test('If there is no query, no change occurs', () => {
const queryRef = inputRef()
const typeaheadRef = inputRef()
renderHook(() => useHighlight(highlighted, true, queryRef, typeaheadRef))
expect(queryRef.current.value).toBe('')
expect(typeaheadRef.current.value).toBe('')
})
test('If selected text does not match typed text, no change occurs', () => {
const queryRef = inputRef('Foo')
const typeaheadRef = inputRef()
renderHook(() => useHighlight(highlighted, true, queryRef, typeaheadRef))
expect(queryRef.current.value).toBe('Foo')
expect(typeaheadRef.current.value).toBe('')
})
})
//////////////////////////////
// Helper functions //
//////////////////////////////
describe('formatQuery', () => {
test('If query does not match typeahead, change the case', () => {
const formattedQuery = formatQuery('chi', 'Chicago')
expect(formattedQuery).toBe('Chi')
})
test('If there is no typeahead, return original query', () => {
const formattedQuery = formatQuery('chi', '')
expect(formattedQuery).toBe('chi')
})
test('If query and typeahead do not match, return original query', () => {
const formattedQuery = formatQuery('chi', 'New York')
expect(formattedQuery).toBe('chi')
})
test('If query is blank, return original query', () => {
const formattedQuery = formatQuery('', 'Chicago')
expect(formattedQuery).toBe('')
})
test('If query matches typeahead, make no change', () => {
const formattedQuery = formatQuery('Chi', 'Chicago')
expect(formattedQuery).toBe('Chi')
})
}) })