From 919a3aa2cd2853bd2bf69cb3e92c5344101bceef Mon Sep 17 00:00:00 2001 From: Krzysztof Kwitt <120908425+krzysztof-kwitt@users.noreply.github.com> Date: Sat, 3 Feb 2024 05:18:42 +0100 Subject: [PATCH] fix(device-page): fix search in header device selector --- .../header-device-selector.test.tsx | 56 +++++++++++++ .../device-page/header-device-selector.tsx | 79 +++++++------------ src/components/device-page/index.tsx | 2 +- 3 files changed, 86 insertions(+), 51 deletions(-) create mode 100644 src/components/device-page/header-device-selector.test.tsx diff --git a/src/components/device-page/header-device-selector.test.tsx b/src/components/device-page/header-device-selector.test.tsx new file mode 100644 index 000000000..9686de5c9 --- /dev/null +++ b/src/components/device-page/header-device-selector.test.tsx @@ -0,0 +1,56 @@ +// import '@testing-library/jest-dom'; +import React from 'react'; +import { createMockDevice, fireEvent, render, screen, waitFor } from '../../test-utils'; +import { it, expect } from 'vitest'; +import { HeaderDeviceSelector } from './header-device-selector'; + +it('all devices are visible in dropdown', async () => { + const ieeeAddress1 = 'FF:FF:FF:FF:FF:AA'; + const ieeeAddress2 = 'AA:AA:AA:AA:AA:AA'; + const currentDevice = createMockDevice({ieee_address: ieeeAddress1, friendly_name: "Button"}) + const devices = { + [ieeeAddress1]: currentDevice, + [ieeeAddress2]: createMockDevice({ieee_address: ieeeAddress2, friendly_name: "Light"}), + } + render( + , + ); + await waitFor(() => fireEvent.click(screen.getByLabelText(/Select a device/))); + expect(document.querySelectorAll('.dropdown-item')).toHaveLength(2) +}); + +it('device can be selected', async () => { + const ieeeAddress1 = 'FF:FF:FF:FF:FF:AA'; + const ieeeAddress2 = 'AA:AA:AA:AA:AA:AA'; + const currentDevice = createMockDevice({ieee_address: ieeeAddress1, friendly_name: "Button"}) + const devices = { + [ieeeAddress1]: currentDevice, + [ieeeAddress2]: createMockDevice({ieee_address: ieeeAddress2, friendly_name: "Light"}), + } + + render( + , + ); + + await waitFor(() => fireEvent.click(screen.getByLabelText(/Select a device/))); + await waitFor(() => fireEvent.click(screen.getByText(/Light/))); + expect(document.location.toString()).toContain(ieeeAddress2) +}); + +it('devices list can be filtered', async () => { + const ieeeAddress1 = 'FF:FF:FF:FF:FF:AA'; + const ieeeAddress2 = 'AA:AA:AA:AA:AA:AA'; + const currentDevice = createMockDevice({ieee_address: ieeeAddress1, friendly_name: "Button"}) + const devices = { + [ieeeAddress1]: currentDevice, + [ieeeAddress2]: createMockDevice({ieee_address: ieeeAddress2, friendly_name: "Light"}), + } + render( + , + ); + + await waitFor(() => fireEvent.click(screen.getByLabelText(/Select a device/))); + await waitFor(() => fireEvent.change(screen.getByPlaceholderText(/Type to filter.../), {target: {value: 'Light'}})); + + expect(document.querySelectorAll('.dropdown-item')).toHaveLength(1) +}); \ No newline at end of file diff --git a/src/components/device-page/header-device-selector.tsx b/src/components/device-page/header-device-selector.tsx index 890c67db5..79f55dbb7 100644 --- a/src/components/device-page/header-device-selector.tsx +++ b/src/components/device-page/header-device-selector.tsx @@ -1,90 +1,69 @@ -import React, { CSSProperties, PropsWithChildren, forwardRef, useState } from 'react'; +import React, { useState } from 'react'; import Dropdown from 'react-bootstrap/Dropdown'; import Form from 'react-bootstrap/Form'; import { Devices } from '../../store'; import { TabName } from './types'; -import { WithTranslation } from 'react-i18next'; import { TFunction } from 'i18next'; +import { Device } from '../../types'; +import Button from '../button'; +import { withTranslation } from 'react-i18next'; -interface SearchMenuProps { - style?: CSSProperties; - className?: string; - searchTerm?: string; - setSearchTerm: (value: string) => void; - t: TFunction; -} interface HeaderDeviceSelectorProps { - devices: Devices; - dev: string; + allDevices: Devices; + currentDevice: Device; tab?: TabName; t: TFunction; } interface HeaderDeviceSelectorItemsProps { - devices: Devices; - dev: string; + devices: Device[]; + currentDevice: Device; tab?: TabName; setSearchTerm: (value: string) => void; } -const SearchMenu = forwardRef>(function SearchMenu( - { children, style, className, searchTerm, setSearchTerm, t }, - ref, -) { - return ( -
- setSearchTerm(e.target.value)} - value={searchTerm} - /> -
    - {React.Children.toArray(children).filter( - (child) => - !searchTerm || - (React.isValidElement(child) && - child.props.children.toLowerCase().includes(searchTerm.toLowerCase())), - )} -
-
- ); -}); - -export function HeaderDeviceSelector(props: HeaderDeviceSelectorProps): JSX.Element { - const { devices, dev, tab = 'info', t } = props; +export function HeaderDeviceSelectorRaw(props: HeaderDeviceSelectorProps): JSX.Element { + const { allDevices, currentDevice, tab = 'info', t } = props; const [searchTerm, setSearchTerm] = useState(''); - const device = devices[dev]; + const selectedDevices = Object.values(allDevices).filter(d => d.friendly_name.includes(searchTerm)) return (

- {device.friendly_name}{' '} + {currentDevice.friendly_name}{' '} - - + + setSearchTerm(e.target.value)} + value={searchTerm} + /> +

); } -function HeaderDeviceSelectorItems({ devices, dev, tab, setSearchTerm }: HeaderDeviceSelectorItemsProps): JSX.Element { +export const HeaderDeviceSelector = withTranslation('devicePage')(HeaderDeviceSelectorRaw); + +function HeaderDeviceSelectorItems({ devices, currentDevice, tab, setSearchTerm }: HeaderDeviceSelectorItemsProps): JSX.Element { return ( <> - {Object.entries(devices).map(([id, device]) => ( + {Object.values(devices).map(listDevice => ( setSearchTerm('')} > - {device.friendly_name} + {listDevice.friendly_name} ))} diff --git a/src/components/device-page/index.tsx b/src/components/device-page/index.tsx index 22594f1e2..664b3f2f4 100644 --- a/src/components/device-page/index.tsx +++ b/src/components/device-page/index.tsx @@ -140,7 +140,7 @@ export function DevicePage(props: DevicePageProps): JSX.Element { return ( <> - +
    {links.map((link) => (