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

fkhadra / react-toastify / 12108230902

01 Dec 2024 07:04PM UTC coverage: 89.219% (+0.2%) from 88.998%
12108230902

Pull #1178

github

fkhadra
refactor: remove activeToasts array
Pull Request #1178: [WIP] V11

344 of 414 branches covered (83.09%)

Branch coverage included in aggregate %.

60 of 62 new or added lines in 14 files covered. (96.77%)

4 existing lines in 3 files now uncovered.

467 of 495 relevant lines covered (94.34%)

1089.07 hits per line

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

94.59
/src/core/containerObserver.ts
1
import { cloneElement, isValidElement, ReactElement } from 'react';
2
import {
3
  Id,
4
  NotValidatedToastProps,
5
  OnChangeCallback,
6
  Toast,
7
  ToastContainerProps,
8
  ToastContent,
9
  ToastProps
10
} from '../types';
11
import { canBeRendered, getAutoCloseDelay, isFn, isNum, isStr, parseClassName, toToastItem } from '../utils';
12

13
type Notify = () => void;
14

15
export type ContainerObserver = ReturnType<typeof createContainerObserver>;
16

17
export function createContainerObserver(
18
  id: Id,
19
  containerProps: ToastContainerProps,
20
  dispatchChanges: OnChangeCallback
21
) {
22
  let toastKey = 1;
956✔
23
  let toastCount = 0;
956✔
24
  let queue: Toast[] = [];
956✔
25
  let snapshot: Toast[] = [];
956✔
26
  let props = containerProps;
956✔
27
  const toasts = new Map<Id, Toast>();
956✔
28
  const listeners = new Set<Notify>();
956✔
29

30
  const observe = (notify: Notify) => {
956✔
31
    listeners.add(notify);
956✔
32
    return () => listeners.delete(notify);
956✔
33
  };
34

35
  const notify = () => {
956✔
36
    snapshot = Array.from(toasts.values());
2,042✔
37
    listeners.forEach(cb => cb());
2,042✔
38
  };
39

40
  const shouldIgnoreToast = ({ containerId, toastId, updateId }: NotValidatedToastProps) => {
956✔
41
    const containerMismatch = containerId ? containerId !== id : id !== 1;
1,808✔
42
    const isDuplicate = toasts.has(toastId) && updateId == null;
1,808✔
43

44
    return containerMismatch || isDuplicate;
1,808✔
45
  };
46

47
  const toggle = (v: boolean, id?: Id) => {
956✔
48
    toasts.forEach(t => {
80✔
49
      if (id == null || id === t.props.toastId) t.toggle?.(v);
80✔
50
    });
51
  };
52

53
  const markAsRemoved = (v: Toast) => {
956✔
54
    v.props?.onClose?.(v.removedByUser);
294✔
55
    v.isActive = false;
294✔
56
  };
57

58
  const removeToast = (id?: Id) => {
956✔
59
    if (id == null) {
280✔
60
      toasts.forEach(markAsRemoved);
74✔
61
    } else {
62
      const t = toasts.get(id);
206✔
63
      if (t) markAsRemoved(t);
206✔
64
    }
65
    notify();
280✔
66
  };
67

68
  const clearQueue = () => {
956✔
69
    toastCount -= queue.length;
16✔
70
    queue = [];
16✔
71
  };
72

73
  const addActiveToast = (toast: Toast) => {
956✔
74
    const { toastId, updateId } = toast.props;
1,478✔
75
    const isNew = updateId == null;
1,478✔
76

77
    if (toast.staleId) toasts.delete(toast.staleId);
1,478✔
78
    toast.isActive = true;
1,478✔
79

80
    toasts.set(toastId, toast);
1,478✔
81
    notify();
1,478✔
82
    dispatchChanges(toToastItem(toast, isNew ? 'added' : 'updated'));
1,478✔
83

84
    if (isNew) toast.props.onOpen?.();
1,478✔
85
  };
86

87
  const buildToast = <TData = unknown>(content: ToastContent<TData>, options: NotValidatedToastProps) => {
956✔
88
    if (shouldIgnoreToast(options)) return;
1,808✔
89

90
    const { toastId, updateId, data, staleId, delay } = options;
1,504✔
91
    const closeToast = (removedByUser?: true) => {
1,504✔
92
      toasts.get(toastId)!.removedByUser = removedByUser;
76✔
93
      removeToast(toastId);
76✔
94
    };
95

96
    const isNotAnUpdate = updateId == null;
1,504✔
97

98
    if (isNotAnUpdate) toastCount++;
1,504✔
99

100
    const toastProps = {
1,504✔
101
      ...props,
102
      style: props.toastStyle,
103
      key: toastKey++,
104
      ...Object.fromEntries(Object.entries(options).filter(([_, v]) => v != null)),
9,584✔
105
      toastId,
106
      updateId,
107
      data,
108
      closeToast,
109
      isIn: false,
110
      className: parseClassName(options.className || props.toastClassName),
1,474✔
111
      bodyClassName: parseClassName(options.bodyClassName || props.bodyClassName),
1,485✔
112
      progressClassName: parseClassName(options.progressClassName || props.progressClassName),
1,485✔
113
      autoClose: options.isLoading ? false : getAutoCloseDelay(options.autoClose, props.autoClose),
752✔
114
      deleteToast() {
115
        const toastToRemove = toasts.get(toastId);
292✔
116

117
        if (toastToRemove == null) return;
292!
118

119
        dispatchChanges(toToastItem(toastToRemove, 'removed'));
292✔
120
        toasts.delete(toastId);
292✔
121

122
        toastCount--;
292✔
123
        if (toastCount < 0) toastCount = 0;
292!
124

125
        if (queue.length > 0) {
292✔
126
          addActiveToast(queue.shift());
8✔
127
          return;
8✔
128
        }
129

130
        notify();
284✔
131
      }
132
    } as ToastProps;
133

134
    toastProps.closeButton = props.closeButton;
1,504✔
135

136
    if (options.closeButton === false || canBeRendered(options.closeButton)) {
1,504✔
137
      toastProps.closeButton = options.closeButton;
62✔
138
    } else if (options.closeButton === true) {
1,442✔
139
      toastProps.closeButton = canBeRendered(props.closeButton) ? props.closeButton : true;
184!
140
    }
141

142
    let toastContent = content;
1,504✔
143

144
    if (isValidElement(content) && !isStr(content.type)) {
1,504!
UNCOV
145
      toastContent = cloneElement(content as ReactElement, {
×
146
        closeToast,
147
        toastProps,
148
        data
149
      });
150
    } else if (isFn(content)) {
1,504✔
151
      toastContent = content({ closeToast, toastProps, data: data as TData });
62✔
152
    }
153

154
    const activeToast = {
1,504✔
155
      content: toastContent,
156
      props: toastProps,
157
      staleId
158
    } as Toast;
159

160
    // not handling limit + delay by design. Waiting for user feedback first
161
    if (props.limit && props.limit > 0 && toastCount > props.limit && isNotAnUpdate) {
1,504✔
162
      queue.push(activeToast);
34✔
163
    } else if (isNum(delay)) {
1,470✔
164
      setTimeout(() => {
246✔
165
        addActiveToast(activeToast);
246✔
166
      }, delay);
167
    } else {
168
      addActiveToast(activeToast);
1,224✔
169
    }
170
  };
171

172
  return {
956✔
173
    id,
174
    props,
175
    observe,
176
    toggle,
177
    removeToast,
178
    toasts,
179
    clearQueue,
180
    buildToast,
181
    setProps(p: ToastContainerProps) {
182
      props = p;
2,364✔
183
    },
184
    setToggle: (id: Id, fn: (v: boolean) => void) => {
185
      const t = toasts.get(id);
2,784✔
186
      if (t) t.toggle = fn;
2,784✔
187
    },
188
    isToastActive: (id: Id) => toasts.get(id)?.isActive,
1,776✔
189
    getSnapshot: () => snapshot
10,064✔
190
  };
191
}
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