• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pushkar8723 / no-frills-ui / 20405179841

21 Dec 2025 05:17AM UTC coverage: 88.867% (+48.6%) from 40.235%
20405179841

push

github

web-flow
Unit tests for components (#44)

* Added test for disabled button and badge components

* Added tests for first set of components

* Added check around value update process in input components

* Added test for second set of components

* Added test for 3rd set of components

* Handle open on load for drawer and modal components

* Added tests for Drawer and Modal components

* Added test for layer manager

* Added test for Dialog

* Fix minor issues in Diloag and PromtDialog

* Added tests for dialogs

* Added tests for Toast component

* Implementd queue in Notification Manager

* Added test for Notification

* Fixed Notification export

* Fixed skipped tests

* Quick wins to increase coverage

* Production readiness

* Documented Ref forwading

* Added Compatibility Check Workflow

* Fix react 19 compatibility

* Fix tests for react 19

920 of 1235 branches covered (74.49%)

Branch coverage included in aggregate %.

318 of 371 new or added lines in 37 files covered. (85.71%)

421 existing lines in 38 files now uncovered.

6591 of 7217 relevant lines covered (91.33%)

24.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.08
/src/components/Input/Dropdown.tsx
1
import React, { useEffect, useState } from 'react';
14✔
2
import styled from '@emotion/styled';
14✔
3
import { ExpandMore } from '../../icons';
504✔
4
import { Menu } from '../Menu';
504✔
5
import { Popover, POPOVER_POSITION } from '../Popover';
504✔
6
import Input from './Input';
504!
7

504✔
8
type DropdownProps<T> = React.PropsWithChildren<{
504!
9
    /** Value of the control */
504✔
10
    value?: T | T[];
81✔
11
    /**
504✔
12
     * If multiple elements can be selected
504✔
13
     * @default false
14!
UNCOV
14
     */
×
UNCOV
15
    multiSelect?: boolean;
×
16
    /** Change handler */
×
17
    onChange?: (v: T | T[]) => void;
14✔
18
    /** Label of the control */
14✔
19
    label?: string;
14✔
20
    /** Error message */
14✔
21
    errorText?: string;
14✔
22
    /** Makes field required */
14✔
23
    required?: boolean;
14!
24
    /** Disables the field */
14✔
25
    disabled?: boolean;
14✔
26
}> &
14✔
27
    Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'>;
14✔
28

14✔
29
const ArrowContainer = styled.span`
14✔
30
    position: absolute;
14!
UNCOV
31
    right: 12px;
×
UNCOV
32
    top: 16px;
×
UNCOV
33
    pointer-events: none;
×
UNCOV
34
`;
×
UNCOV
35

×
36
/**
×
37
 * Dropdown component that allows selection from a list of options.
×
38
 * Supports single and multi-select modes.
14✔
39
 *
14✔
40
 * @template T - The type of the value(s) in the dropdown.
14✔
41
 * @param props - The properties for the Dropdown component.
14!
42
 * @returns The rendered Dropdown component.
14✔
43
 */
14✔
44
/**
14✔
45
 * Dropdown Component
14✔
46
 * @template T - The type of value(s) in the dropdown.
504✔
47
 * @param props - Component props
14✔
48
 * @param outerRef - Ref forwarded to the underlying HTMLInputElement
14✔
49
 */
14✔
50
function DropdownComponent<T extends object>(
14✔
51
    props: DropdownProps<T>,
27✔
52
    outerRef: React.Ref<HTMLInputElement>,
68✔
53
) {
68✔
54
    const {
68✔
55
        multiSelect = false,
27!
56
        onChange,
27✔
57
        children,
27✔
58
        value: propValue,
27✔
59
        label,
27✔
60
        errorText,
27✔
61
        required,
27✔
62
        disabled,
27✔
63
        ...rest
27!
UNCOV
64
    } = props;
×
UNCOV
65
    const [open, setOpen] = useState(false);
×
UNCOV
66
    const [value, setValue] = useState<T | T[] | undefined>(propValue);
×
67
    const id = React.useId();
14✔
68
    const menuId = `${id}-menu`;
14✔
69
    const menuRef = React.useRef<HTMLDivElement | null>(null);
14✔
70
    const triggerRef = React.useRef<HTMLInputElement | null>(null);
14!
71

14✔
72
    // Focus menu when opened
14✔
73
    useEffect(() => {
14✔
74
        if (open) {
14✔
75
            // Wait for Popover to fully open and focus itself first
14✔
76
            // Then move focus to the first menu item
14✔
77
            const timer = setTimeout(() => {
14✔
78
                const firstItem = menuRef.current?.querySelector('[role="option"]') as HTMLElement;
14✔
79
                if (firstItem) {
14✔
80
                    firstItem.focus();
14✔
81
                }
14✔
82
            }, 100); // Wait after Popover has set initial focus
14✔
83
            return () => clearTimeout(timer);
14✔
84
        }
14✔
85
    }, [open]);
14✔
86

14✔
87
    /**
14✔
88
     * Handles keydown events on the input trigger.
14✔
89
     * Opens the menu on 'Enter', 'Space', 'ArrowDown', or 'ArrowUp'.
14✔
90
     *
14✔
91
     * @param {React.KeyboardEvent<HTMLInputElement>} e - The keyboard event.
14✔
92
     */
14✔
93
    const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
14✔
94
        if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
14✔
95
            e.preventDefault();
14✔
96
            setOpen(true);
14✔
97
        }
27✔
98
    };
27✔
99

27✔
100
    /**
27✔
101
     * Handles changes to the dropdown value.
27✔
102
     * Updates local state and calls the external onChange handler.
27✔
103
     * Closes the dropdown if not in multi-select mode.
27✔
104
     *
27✔
105
     * @param {T | T[]} val - The new value(s).
27✔
106
     */
27✔
107
    const changeHandler = (val: T | T[]) => {
27✔
108
        setValue(val);
27✔
109
        onChange?.(val);
27✔
110

27✔
111
        // Close dropdown after selection if not multiSelect
27✔
112
        if (!multiSelect) {
27✔
113
            setOpen(false);
27✔
114
            triggerRef.current?.focus();
27✔
115
        }
27✔
116
    };
27✔
117

27✔
118
    /**
27✔
119
     * Toggles the dropdown open state on click.
27✔
120
     */
27✔
121
    const clickHandler = () => setOpen(true);
27✔
122

25✔
123
    const TriggerElement = React.forwardRef<HTMLInputElement>((passedProps, ref) => {
25✔
124
        // Helper to assign both internal triggerRef and external forwarded ref
8✔
125
        const assignRefs = (node: HTMLInputElement | null) => {
1✔
126
            triggerRef.current = node;
1✔
127

1✔
128
            if (!outerRef) return;
1!
129
            if (typeof outerRef === 'function') {
1✔
130
                outerRef(node);
1✔
131
            } else {
1✔
132
                (outerRef as React.MutableRefObject<HTMLInputElement | null>).current = node;
8✔
133
            }
8✔
134
        };
8✔
135

8✔
136
        // Combine the ref passed by parent with our assignRefs so both are updated
27✔
137
        const combinedRef: React.Ref<HTMLInputElement> = (node) => {
27✔
138
            assignRefs(node);
27✔
139
            if (typeof ref === 'function') {
27✔
140
                ref(node);
27✔
141
            } else if (ref) {
27✔
142
                (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;
3✔
143
            }
3✔
144
        };
3✔
145

3✔
146
        return (
3✔
147
            <>
27✔
148
                <Input
27✔
149
                    {...rest}
27✔
150
                    {...passedProps}
27✔
151
                    ref={combinedRef}
27✔
152
                    type="text"
27✔
153
                    value={value && String(value)}
27✔
154
                    label={label}
27✔
155
                    errorText={errorText}
27✔
156
                    onClick={clickHandler}
3✔
157
                    onKeyDown={onKeyDown}
3!
158
                    required={required}
3✔
159
                    disabled={disabled}
3✔
160
                    readOnly
3✔
161
                    role="combobox"
1✔
162
                    aria-haspopup="listbox"
1✔
163
                    aria-expanded={open}
1!
164
                    aria-controls={menuId}
27✔
165
                />
27✔
166
                <ArrowContainer aria-hidden="true">
27✔
167
                    <ExpandMore />
27✔
168
                </ArrowContainer>
27✔
169
            </>
46✔
170
        );
46✔
171
    });
46✔
172
    TriggerElement.displayName = 'DropdownTrigger';
46✔
173

46✔
174
    return (
46✔
175
        <Popover
46✔
176
            position={POPOVER_POSITION.BOTTOM_LEFT}
46✔
177
            open={open}
92✔
178
            element={TriggerElement}
92✔
179
            onClose={() => {
2✔
180
                setOpen(false);
92!
181
                triggerRef.current?.focus();
92✔
182
            }}
2✔
183
        >
2✔
184
            <Menu
2✔
185
                ref={menuRef}
46✔
186
                id={menuId}
46✔
187
                value={value}
46✔
188
                multiSelect={multiSelect}
46✔
189
                onChange={changeHandler}
46✔
190
            >
92✔
191
                {children}
92✔
192
            </Menu>
92!
UNCOV
193
        </Popover>
×
UNCOV
194
    );
×
UNCOV
195
}
×
UNCOV
196

×
197
const Dropdown = React.forwardRef(DropdownComponent) as <T>(
92✔
198
    props: DropdownProps<T> & React.RefAttributes<HTMLInputElement>,
46✔
199
) => React.ReactElement | null;
46✔
200
export default Dropdown;
46✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc