• 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.19
/src/components/Menu/Menu.tsx
1
import React, { useState, ForwardedRef } from 'react';
15✔
2
import styled from '@emotion/styled';
15✔
3
import { getThemeValue, THEME_NAME } from '../../shared/constants';
540✔
4
import MenuContext from './MenuContext';
540✔
5
/**
540✔
6
 * Props for the Menu component.
540!
7
 * @template T - The type of value(s) in the menu.
540!
8
 */
540✔
9
type MenuProps<T> = {
540✔
10
    /** Multiple Menu Items can be selected */
540✔
11
    multiSelect?: boolean;
540✔
12
    /** Value(s) selected */
15!
UNCOV
13
    value?: T | T[];
×
14
    /** Callback when the selected value changes */
15✔
15
    onChange?: (value: T | T[]) => void;
15✔
16
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>;
15✔
17

15✔
18
const MenuContainer = styled.div`
15✔
19
    flex: 1;
15✔
20
    display: flex;
15✔
21
    flex-direction: column;
15!
22

×
23
    & div:last-child {
15✔
24
        border-bottom: none;
15✔
25
    }
15✔
26

15✔
27
    &:focus-within {
15✔
28
        box-shadow: 0 0 0 4px ${getThemeValue(THEME_NAME.PRIMARY_LIGHT)};
15✔
29
    }
15✔
30
`;
15✔
UNCOV
31

×
UNCOV
32
/**
×
UNCOV
33
 * Menu component that allows selection of items from a list.
×
UNCOV
34
 * Supports single and multi-select modes and keyboard navigation.
×
UNCOV
35
 *
×
36
 * @template T - The type of value(s) in the menu.
15✔
37
 * @param props - The menu properties.
15✔
38
 * @param ref - The ref forwarded to the menu container.
15!
39
 */
15✔
40
/**
15✔
41
 * Menu Component
15✔
42
 * @template T - The type of value(s) in the menu.
15✔
43
 * @param props - Component props
15✔
44
 * @param ref - Ref forwarded to the underlying HTMLDivElement
15✔
45
 */
15✔
46
function MenuInner<T>(props: MenuProps<T>, ref: ForwardedRef<HTMLDivElement>) {
15✔
47
    const { multiSelect = false, onChange, value: propValue, children, ...rest } = props;
15✔
48
    // State holds either a single T or an array of T when multiSelect
115✔
49
    const [value, setValue] = useState<unknown | undefined>(propValue);
32✔
50

32✔
51
    /**
32✔
52
     * Updates the selected value(s).
32✔
53
     * Handles both single and multi-select logic.
32!
UNCOV
54
     *
×
UNCOV
55
     * @param {T} val - The value to select or deselect.
×
UNCOV
56
     */
×
UNCOV
57
    const updateValue = (val: unknown) => {
×
58
        let newVal: unknown;
15✔
59
        if (multiSelect) {
15✔
60
            if (Array.isArray(value)) {
15✔
61
                if (value.includes(val as unknown as T)) {
30!
62
                    newVal = (value as T[]).filter((item) => item !== val);
15✔
63
                } else {
15✔
64
                    newVal = [...(value as T[]), val];
15✔
65
                }
15✔
66
            } else {
15✔
67
                newVal = [val];
15✔
68
            }
15✔
69
        } else {
15✔
70
            newVal = val;
15✔
71
        }
15✔
72

15✔
73
        setValue(newVal as T | T[]);
15✔
74
        onChange?.(newVal as T | T[]);
15✔
75
    };
15✔
76

15✔
77
    /**
15✔
78
     * Handles keyboard navigation within the menu.
15✔
79
     * Supports Arrow keys for navigation, and Enter/Space for selection.
15✔
80
     *
15✔
81
     * @param {React.KeyboardEvent} e - The keyboard event.
15✔
82
     */
15✔
83
    const handleKeyDown = (e: React.KeyboardEvent) => {
15✔
84
        const target = e.target as HTMLElement;
15✔
85
        const container = e.currentTarget as HTMLElement;
15✔
86
        const items = Array.from(container.querySelectorAll('[role="option"]')) as HTMLElement[];
15✔
87
        const currentIndex = items.indexOf(target as HTMLElement);
15✔
88

15✔
89
        let nextIndex;
15✔
90

15✔
91
        switch (e.key) {
15✔
92
            case 'ArrowDown':
15✔
93
                e.preventDefault();
15✔
94
                nextIndex = currentIndex + 1;
15✔
95
                if (nextIndex >= items.length) nextIndex = 0;
15✔
96
                items[nextIndex]?.focus();
32✔
97
                break;
32✔
98
            case 'ArrowUp':
32✔
99
                e.preventDefault();
32✔
100
                nextIndex = currentIndex - 1;
32✔
101
                if (nextIndex < 0) nextIndex = items.length - 1;
32✔
102
                items[nextIndex]?.focus();
32✔
103
                break;
32✔
104
            case 'Home':
32✔
105
                e.preventDefault();
32✔
106
                items[0]?.focus();
32✔
107
                break;
32✔
108
            case 'End':
32✔
109
                e.preventDefault();
32✔
110
                items[items.length - 1]?.focus();
32✔
111
                break;
10✔
112
            case 'Enter':
6✔
113
            case ' ': // Space
6✔
114
                e.preventDefault();
3!
UNCOV
115
                target.click();
×
UNCOV
116
                break;
×
117
            default:
3✔
118
                break;
3✔
119
        }
3✔
120
    };
3✔
121

3✔
122
    /**
3✔
123
     * Handles focus events on the menu container.
3✔
124
     * Delegates focus to the first item if the container itself receives focus.
10✔
125
     *
4✔
126
     * @param {React.FocusEvent} e - The focus event.
10✔
127
     */
10✔
128
    const focusHandler = (e: React.FocusEvent) => {
10!
129
        // Prevent trap: If focus came from inside (Shift+Tab), do NOT auto-focus again.
32✔
130
        // This allows focus to land on the container, and the next Shift+Tab will exit.
32✔
131
        if (e.currentTarget.contains(e.relatedTarget as Node)) {
32✔
132
            return;
32✔
133
        }
32✔
134

12✔
135
        // Only if focus is actually on the container (e.g. tabbing into it)
12✔
136
        // and not bubbling up from a child
12✔
137
        if (e.target === e.currentTarget) {
12✔
138
            // Prevent the container from holding focus; delegate to first item
12✔
139
            const firstItem = e.currentTarget.querySelector('[role="option"]') as HTMLElement;
12✔
140
            firstItem?.focus();
4✔
141
        }
4✔
142
    };
4✔
143

4✔
144
    return (
4✔
145
        // @ts-expect-error Generic context typing
4✔
146
        <MenuContext.Provider value={{ value: value, multiSelect: !!multiSelect, updateValue }}>
4!
147
            <MenuContainer
4✔
148
                {...rest}
12✔
149
                ref={ref}
2✔
150
                role="listbox"
2✔
151
                aria-multiselectable={multiSelect}
2✔
152
                tabIndex={0}
2✔
153
                onKeyDown={handleKeyDown}
2✔
154
                onFocus={focusHandler}
2✔
155
            >
2✔
156
                {children}
2!
157
            </MenuContainer>
2✔
158
        </MenuContext.Provider>
12✔
159
    );
1✔
160
}
1✔
161

1✔
162
const Menu = React.forwardRef(MenuInner) as <T>(
1✔
163
    props: MenuProps<T> & React.RefAttributes<HTMLDivElement>,
1!
164
) => React.ReactElement;
12✔
165

12✔
166
export default Menu;
12✔
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