• 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

84.39
/src/components/Notification/Notification.tsx
1
import { type RefCallback } from 'react';
3✔
2
import { flushSync } from 'react-dom';
3✔
3
import { createRoot, type Root } from 'react-dom/client';
3✔
4
import LayerManager, { LAYER_POSITION } from '../../shared/LayerManager';
×
5
import NotificationManager from './NotificationManager';
×
6
import { NOTIFICATION_POSITION, NOTIFICATION_TYPE, NotificationOptions } from './types';
×
UNCOV
7

×
8
type NotificationProps = {
×
9
    /** Title of the notification */
3!
10
    title: string;
×
11
    /** Body of the notification */
×
12
    description: string;
3✔
13
    /** Id for the notification, helps in de-duplication. */
3✔
14
    id?: string;
3✔
UNCOV
15
    /**
×
16
     * Duration for the notification in milliseconds
×
17
     * @default 5000
3✔
UNCOV
18
     */
×
UNCOV
19
    duration?: number;
×
20
    /**
×
21
     * Creates sticky notification
3✔
22
     * @default false
3✔
23
     */
3✔
24
    sticky?: boolean;
3✔
25
    /**
3✔
UNCOV
26
     * Type of notification
×
UNCOV
27
     * @default NOTIFICATION_TYPE.INFO
×
UNCOV
28
     */
×
29
    type?: NOTIFICATION_TYPE;
×
30
    /** Action button text */
×
31
    buttonText?: string;
×
32
    /** Action button click callback */
×
UNCOV
33
    buttonClick?: () => void;
×
34
    /** Notification close callback. */
3✔
35
    onClose?: () => void;
3✔
36
    /** Aria label for the close button on the notification. Defaults to "Close notification" */
3!
UNCOV
37
    closeButtonAriaLabel?: string;
×
UNCOV
38
};
×
UNCOV
39

×
UNCOV
40
/**
×
UNCOV
41
 * This dummy component is used to extract props for documentation in Storybook.
×
UNCOV
42
 * @param props
×
UNCOV
43
 * @returns
×
UNCOV
44
 */
×
45
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3✔
46
export function StoryProps(props: NotificationProps) {
3✔
47
    return null;
3✔
48
}
3✔
49

3✔
50
/** Maps notification position to layer position */
3!
51
const positionMap = {
3✔
52
    [NOTIFICATION_POSITION.TOP_LEFT]: LAYER_POSITION.TOP_LEFT,
3✔
53
    [NOTIFICATION_POSITION.TOP_RIGHT]: LAYER_POSITION.TOP_RIGHT,
3✔
54
    [NOTIFICATION_POSITION.BOTTOM_LEFT]: LAYER_POSITION.BOTTOM_LEFT,
3✔
55
    [NOTIFICATION_POSITION.BOTTOM_RIGHT]: LAYER_POSITION.BOTTOM_RIGHT,
3✔
56
};
3✔
57

3✔
58
/** Notification class */
3✔
59
class Notification {
3✔
60
    /** Helps in maintaining single instance for different positions. */
3✔
61
    private containers: Map<
3✔
62
        NOTIFICATION_POSITION,
3✔
63
        {
3✔
64
            manager: NotificationManager | null;
3✔
65
            root: Root;
3✔
66
            div: HTMLDivElement;
3✔
67
        }
3✔
68
    > = new Map();
1✔
69

1✔
70
    /** Pending add requests waiting for manager to mount */
3✔
71
    private pending: Map<NOTIFICATION_POSITION, Array<(manager: NotificationManager) => void>> =
3✔
72
        new Map();
3✔
73

3✔
74
    /**
3✔
75
     * Adds a notification
3✔
76
     *
3✔
77
     * @param position - The position where the notification should appear
3✔
78
     * @param options - Configuration options for the notification
3✔
79
     * @returns The notification ID or a promise that resolves to the notification ID
3✔
80
     */
3✔
81
    public add = (
3✔
82
        position: NOTIFICATION_POSITION,
3✔
83
        options: NotificationOptions,
3✔
84
        ariaLabel: string = 'Notifications',
3✔
85
    ) => {
3✔
86
        if (!this.containers.has(position)) {
3✔
87
            /** Callback ref to capture the NotificationManager instance when it mounts */
3✔
88
            const refCallback: RefCallback<NotificationManager> = (instance) => {
3✔
89
                if (instance) {
3✔
90
                    const container = this.containers.get(position);
3✔
91
                    if (container) {
3✔
92
                        container.manager = instance;
3✔
93
                    }
3✔
94

3✔
95
                    // Process pending requests
3✔
96
                    const queue = this.pending.get(position);
13✔
97
                    if (queue) {
13✔
98
                        queue.forEach((cb) => cb(instance));
10✔
99
                        this.pending.delete(position);
10✔
100
                    }
20✔
101
                }
20✔
102
            };
20✔
103

10✔
104
            const [Component] = LayerManager.renderLayer({
10✔
105
                closeOnEsc: false,
10✔
106
                closeOnOverlayClick: false,
10✔
107
                position: positionMap[position],
10✔
108
                alwaysOnTop: true,
10✔
109
                component: (
10✔
110
                    <NotificationManager
10✔
111
                        ref={refCallback}
10✔
112
                        position={position}
10✔
113
                        onEmpty={() => this.destroy(position)}
10✔
114
                        ariaLabel={ariaLabel}
10✔
115
                    />
10✔
116
                ),
20✔
117
            });
10✔
118

10✔
119
            // Create a div to mount the Component
10✔
120
            const div = document.createElement('div');
10✔
121
            document.body.appendChild(div);
10✔
122
            const root = createRoot(div);
10✔
123

10✔
124
            this.containers.set(position, {
10✔
125
                manager: null,
10✔
126
                root,
10✔
127
                div,
10✔
128
            });
10✔
129

10✔
130
            // Render the Component which will trigger the LayerManager's useEffect
10✔
131
            flushSync(() => {
10✔
132
                root.render(<Component />);
10✔
133
            });
10✔
134
        }
10✔
135

10✔
136
        const container = this.containers.get(position);
10✔
137
        if (container && container.manager) {
10✔
138
            return container.manager.add(options);
10✔
139
        }
10✔
140

10✔
141
        // If manager is not ready yet, add to pending queue
10✔
142
        return new Promise<string>((resolve) => {
10✔
143
            const queue = this.pending.get(position) || [];
10✔
144
            queue.push((manager) => {
10✔
145
                resolve(manager.add(options));
10✔
146
            });
10✔
147
            this.pending.set(position, queue);
10✔
148
        });
10✔
149
    };
10✔
150

10✔
151
    /**
10✔
152
     * Removes a notification
10✔
153
     *
10✔
154
     * @param position - The position of the notification container
13✔
155
     * @param id - The unique ID of the notification to remove
13✔
156
     */
3✔
157
    public remove = (position: NOTIFICATION_POSITION, id: string) => {
13✔
158
        const container = this.containers.get(position);
10✔
159
        if (container && container.manager) {
10✔
160
            container.manager.remove(id);
10✔
161
        }
10✔
162
    };
10✔
163

10✔
164
    /**
10✔
165
     * Destroys entire stack of notifications at a position.
10✔
166
     * Unmounts the React root and cleans up DOM elements.
10✔
167
     *
10✔
168
     * @param position - The position of the notification container to destroy
3✔
169
     */
3✔
170
    public destroy = (position: NOTIFICATION_POSITION) => {
3✔
171
        const container = this.containers.get(position);
3✔
172
        if (container) {
3✔
173
            container.root.unmount();
3✔
174
            if (document.body.contains(container.div)) {
3✔
175
                document.body.removeChild(container.div);
1✔
176
            }
1✔
177
            this.containers.delete(position);
1✔
178
        }
1✔
179
    };
1✔
180
}
1✔
181

1✔
182
/** Export a singleton instance */
3✔
183
export default new Notification();
3✔
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