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

fkhadra / react-toastify / 7115567174

06 Dec 2023 01:46PM UTC coverage: 89.273% (+0.5%) from 88.779%
7115567174

Pull #968

github

fkhadra
tweak progress bar
Pull Request #968: [WIP] V10

262 of 317 branches covered (0.0%)

Branch coverage included in aggregate %.

225 of 236 new or added lines in 13 files covered. (95.34%)

1 existing line in 1 file now uncovered.

462 of 494 relevant lines covered (93.52%)

497.33 hits per line

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

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

21
interface QueuedToast {
22
  content: ToastContent<any>;
23
  props: ToastProps;
24
  staleId?: Id;
25
}
26

27
type Notify = () => void;
28

29
interface ActiveToast {
30
  content: ToastContent<any>;
31
  props: ToastProps;
32
  staleId?: Id;
33
}
34

35
export type ContainerObserver = ReturnType<typeof createContainerObserver>;
36

37
export function createContainerObserver(
38
  id: Id,
39
  containerProps: ToastContainerProps,
40
  dispatchChanges: OnChangeCallback
41
) {
42
  let toastKey = 1;
409✔
43
  let toastCount = 0;
409✔
44
  let queue: QueuedToast[] = [];
409✔
45
  let activeToasts: Id[] = [];
409✔
46
  let snapshot: Toast[] = [];
409✔
47
  let props = containerProps;
409✔
48
  const toasts = new Map<Id, Toast>();
409✔
49
  const listeners = new Set<Notify>();
409✔
50

51
  const observe = (notify: Notify) => {
409✔
52
    listeners.add(notify);
409✔
53
    return () => listeners.delete(notify);
409✔
54
  };
55

56
  const notify = () => {
409✔
57
    snapshot = Array.from(toasts.values());
908✔
58
    listeners.forEach(cb => cb());
908✔
59
  };
60

61
  const shouldIgnoreToast = ({
409✔
62
    containerId,
63
    toastId,
64
    updateId
65
  }: NotValidatedToastProps) => {
66
    const containerMismatch = containerId ? containerId !== id : id !== 1;
746✔
67
    const isDuplicate = toasts.has(toastId) && updateId == null;
746✔
68

69
    return containerMismatch || isDuplicate;
746✔
70
  };
71

72
  const toggle = (v: boolean, id?: Id) => {
409✔
73
    toasts.forEach(t => {
38✔
74
      if (id == null || id === t.props.toastId) isFn(t.toggle) && t.toggle(v);
38✔
75
    });
76
  };
77

78
  const removeToast = (id?: Id) => {
409✔
79
    activeToasts = id == null ? [] : activeToasts.filter(v => v !== id);
110✔
80
    notify();
110✔
81
  };
82

83
  const clearQueue = () => {
409✔
84
    toastCount -= queue.length;
8✔
85
    queue = [];
8✔
86
  };
87

88
  const addActiveToast = (toast: ActiveToast) => {
409✔
89
    const { toastId, onOpen, updateId, children } = toast.props;
680✔
90
    const isNew = updateId == null;
680✔
91

92
    if (toast.staleId) toasts.delete(toast.staleId);
680✔
93

94
    toasts.set(toastId, toast);
680✔
95
    activeToasts = [...activeToasts, toast.props.toastId].filter(
680✔
96
      v => v !== toast.staleId
1,220✔
97
    );
98
    notify();
680✔
99
    dispatchChanges(toToastItem(toast, isNew ? 'added' : 'updated'));
680✔
100

101
    if (isNew && isFn(onOpen))
680✔
102
      onOpen(isValidElement(children) && children.props);
13!
103
  };
104

105
  const buildToast = <TData = unknown>(
409✔
106
    content: ToastContent<TData>,
107
    options: NotValidatedToastProps
108
  ) => {
109
    if (shouldIgnoreToast(options)) return;
746✔
110

111
    const { toastId, updateId, data, staleId, delay } = options;
693✔
112
    const closeToast = () => {
693✔
113
      removeToast(toastId);
37✔
114
    };
115

116
    const isNotAnUpdate = updateId == null;
693✔
117

118
    if (isNotAnUpdate) toastCount++;
693✔
119

120
    const toastProps = {
693✔
121
      ...props,
122
      style: props.toastStyle,
123
      key: toastKey++,
124
      ...Object.fromEntries(
125
        Object.entries(options).filter(([_, v]) => v != null)
4,420✔
126
      ),
127
      toastId,
128
      updateId,
129
      data,
130
      closeToast,
131
      isIn: false,
132
      className: parseClassName(options.className || props.toastClassName),
1,358✔
133
      bodyClassName: parseClassName(
134
        options.bodyClassName || props.bodyClassName
1,368✔
135
      ),
136
      progressClassName: parseClassName(
137
        options.progressClassName || props.progressClassName
1,368✔
138
      ),
139
      autoClose: options.isLoading
693✔
140
        ? false
141
        : getAutoCloseDelay(options.autoClose, props.autoClose),
142
      deleteToast() {
143
        const toastToRemove = toasts.get(toastId)!;
122✔
144
        const { onClose, children } = toastToRemove.props;
122✔
145
        if (isFn(onClose)) onClose(isValidElement(children) && children.props);
122!
146

147
        dispatchChanges(toToastItem(toastToRemove, 'removed'));
122✔
148
        toasts.delete(toastId);
122✔
149

150
        toastCount--;
122✔
151
        if (toastCount < 0) toastCount = 0;
122!
152

153
        if (queue.length > 0) {
122✔
154
          addActiveToast(queue.shift() as ActiveToast);
4✔
155
          return;
4✔
156
        }
157

158
        notify();
118✔
159
      }
160
    } as ToastProps;
161

162
    toastProps.closeButton = props.closeButton;
693✔
163

164
    if (options.closeButton === false || canBeRendered(options.closeButton)) {
693✔
165
      toastProps.closeButton = options.closeButton;
29✔
166
    } else if (options.closeButton === true) {
664✔
167
      toastProps.closeButton = canBeRendered(props.closeButton)
85!
168
        ? props.closeButton
169
        : true;
170
    }
171

172
    let toastContent = content;
693✔
173

174
    if (isValidElement(content) && !isStr(content.type)) {
693!
NEW
175
      toastContent = cloneElement(content as ReactElement, {
×
176
        closeToast,
177
        toastProps,
178
        data
179
      });
180
    } else if (isFn(content)) {
693✔
181
      toastContent = content({ closeToast, toastProps, data: data as TData });
29✔
182
    }
183

184
    const activeToast = {
693✔
185
      content: toastContent,
186
      props: toastProps,
187
      staleId
188
    };
189

190
    // not handling limit + delay by design. Waiting for user feedback first
191
    if (
693✔
192
      props.limit &&
858✔
193
      props.limit > 0 &&
194
      toastCount > props.limit &&
195
      isNotAnUpdate
196
    ) {
197
      queue.push(activeToast);
17✔
198
    } else if (isNum(delay)) {
676✔
199
      setTimeout(() => {
114✔
200
        addActiveToast(activeToast);
114✔
201
      }, delay);
202
    } else {
203
      addActiveToast(activeToast);
562✔
204
    }
205
  };
206

207
  return {
409✔
208
    id,
209
    props,
210
    observe,
211
    toggle,
212
    removeToast,
213
    toasts,
214
    clearQueue,
215
    buildToast,
216
    setProps(p: ToastContainerProps) {
217
      props = p;
1,033✔
218
    },
219
    setToggle: (id: Id, fn: (v: boolean) => void) => {
220
      toasts.get(id)!.toggle = fn;
1,257✔
221
    },
222
    isToastActive: (id: Id) => activeToasts.some(v => v === id),
1,107✔
223
    getSnapshot: () => (props.newestOnTop ? snapshot.reverse() : snapshot)
4,416!
224
  };
225
}
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