Notification.spec.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. * Copyright 2021-2025 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React from "react";
  14. import { render, screen, waitFor } from "@testing-library/react";
  15. import "@testing-library/jest-dom";
  16. import { SnackbarProvider } from "notistack";
  17. import TaipyNotification from "./Notification";
  18. import { NotificationMessage } from "../../context/taipyReducers";
  19. import userEvent from "@testing-library/user-event";
  20. const defaultMessage = "message";
  21. const defaultNotifications: NotificationMessage[] = [
  22. { nType: "success", message: defaultMessage, system: true, duration: 3000, snackbarId: "nId" },
  23. ];
  24. const getNotificationsWithType = (nType: string) => [{ ...defaultNotifications[0], nType }];
  25. class myNotification {
  26. static requestPermission = jest.fn(() => Promise.resolve("granted"));
  27. static permission = "granted";
  28. }
  29. describe("Notifications", () => {
  30. beforeAll(() => {
  31. globalThis.Notification = myNotification as unknown as jest.Mocked<typeof Notification>;
  32. });
  33. beforeEach(() => {
  34. jest.clearAllMocks();
  35. });
  36. it("renders", async () => {
  37. const { getByText } = render(
  38. <SnackbarProvider>
  39. <TaipyNotification notifications={defaultNotifications} />
  40. </SnackbarProvider>
  41. );
  42. const elt = getByText(defaultMessage);
  43. expect(elt.tagName).toBe("DIV");
  44. });
  45. it("displays a success notification", async () => {
  46. const { getByText } = render(
  47. <SnackbarProvider>
  48. <TaipyNotification notifications={defaultNotifications} />
  49. </SnackbarProvider>
  50. );
  51. const elt = getByText(defaultMessage);
  52. expect(elt.closest(".notistack-MuiContent-success")).toBeInTheDocument();
  53. });
  54. it("displays an error notification", async () => {
  55. const { getByText } = render(
  56. <SnackbarProvider>
  57. <TaipyNotification notifications={getNotificationsWithType("error")} />
  58. </SnackbarProvider>
  59. );
  60. const elt = getByText(defaultMessage);
  61. expect(elt.closest(".notistack-MuiContent-error")).toBeInTheDocument();
  62. });
  63. it("displays a warning notification", async () => {
  64. const { getByText } = render(
  65. <SnackbarProvider>
  66. <TaipyNotification notifications={getNotificationsWithType("warning")} />
  67. </SnackbarProvider>
  68. );
  69. const elt = getByText(defaultMessage);
  70. expect(elt.closest(".notistack-MuiContent-warning")).toBeInTheDocument();
  71. });
  72. it("displays an info notification", async () => {
  73. const { getByText } = render(
  74. <SnackbarProvider>
  75. <TaipyNotification notifications={getNotificationsWithType("info")} />
  76. </SnackbarProvider>
  77. );
  78. const elt = getByText(defaultMessage);
  79. expect(elt.closest(".notistack-MuiContent-info")).toBeInTheDocument();
  80. });
  81. it("gets favicon URL from document link tags", () => {
  82. const link = document.createElement("link");
  83. link.rel = "icon";
  84. link.href = "/test-icon.png";
  85. document.head.appendChild(link);
  86. const notifications: NotificationMessage[] = [
  87. {
  88. nType: "success",
  89. message: "This is a system notification",
  90. system: true,
  91. duration: 3000,
  92. snackbarId: "nId",
  93. },
  94. ];
  95. render(
  96. <SnackbarProvider>
  97. <TaipyNotification notifications={notifications} />
  98. </SnackbarProvider>
  99. );
  100. const linkElement = document.querySelector("link[rel='icon']");
  101. if (linkElement) {
  102. expect(linkElement.getAttribute("href")).toBe("/test-icon.png");
  103. } else {
  104. expect(true).toBe(false);
  105. }
  106. document.head.removeChild(link);
  107. });
  108. it("closes notification on close button click", async () => {
  109. const notifications = [
  110. { nType: "success", message: "Test Notification", duration: 3000, system: false, snackbarId: "nId" },
  111. ];
  112. render(
  113. <SnackbarProvider>
  114. <TaipyNotification notifications={notifications} />
  115. </SnackbarProvider>
  116. );
  117. const closeButton = await screen.findByRole("button", { name: /close/i });
  118. await userEvent.click(closeButton);
  119. await waitFor(() => {
  120. const notificationMessage = screen.queryByText("Test Notification");
  121. expect(notificationMessage).not.toBeInTheDocument();
  122. });
  123. });
  124. it("Notification disappears when notification type is empty", async () => {
  125. const baseNotification = {
  126. nType: "success",
  127. message: "Test Notification",
  128. duration: 3000,
  129. system: false,
  130. notificationId: "nId",
  131. snackbarId: "nId",
  132. };
  133. const notifications = [ baseNotification ];
  134. const { rerender } = render(
  135. <SnackbarProvider>
  136. <TaipyNotification notifications={notifications} />
  137. </SnackbarProvider>
  138. );
  139. await screen.findByRole("button", { name: /close/i });
  140. const newNotifications = [ { ...baseNotification, nType: "" }];
  141. rerender(
  142. <SnackbarProvider>
  143. <TaipyNotification notifications={newNotifications} />
  144. </SnackbarProvider>
  145. );
  146. await waitFor(() => {
  147. const notificationMessage = screen.queryByText("Test Notification");
  148. expect(notificationMessage).not.toBeInTheDocument();
  149. });
  150. });
  151. it("does nothing when notification is undefined", async () => {
  152. render(
  153. <SnackbarProvider>
  154. <TaipyNotification notifications={[]} />
  155. </SnackbarProvider>
  156. );
  157. expect(Notification.requestPermission).not.toHaveBeenCalled();
  158. });
  159. it("validates href when rel attribute is 'icon' and href is set", () => {
  160. const link = document.createElement("link");
  161. link.rel = "icon";
  162. link.href = "/test-icon.png";
  163. document.head.appendChild(link);
  164. const notifications: NotificationMessage[] = [
  165. {
  166. nType: "success",
  167. message: "This is a system notification",
  168. system: true,
  169. duration: 3000,
  170. snackbarId: "nId",
  171. },
  172. ];
  173. render(
  174. <SnackbarProvider>
  175. <TaipyNotification notifications={notifications} />
  176. </SnackbarProvider>
  177. );
  178. const linkElement = document.querySelector("link[rel='icon']");
  179. expect(linkElement?.getAttribute("href")).toBe("/test-icon.png");
  180. document.head.removeChild(link);
  181. });
  182. it("verifies default favicon for 'icon' rel attribute when href is unset/empty", () => {
  183. const link = document.createElement("link");
  184. link.rel = "icon";
  185. document.head.appendChild(link);
  186. const notifications: NotificationMessage[] = [
  187. {
  188. nType: "success",
  189. message: "This is a system notification",
  190. system: true,
  191. duration: 3000,
  192. snackbarId: "nId",
  193. },
  194. ];
  195. render(
  196. <SnackbarProvider>
  197. <TaipyNotification notifications={notifications} />
  198. </SnackbarProvider>
  199. );
  200. const linkElement = document.querySelector("link[rel='icon']");
  201. expect(linkElement?.getAttribute("href") || "/favicon.png").toBe("/favicon.png");
  202. document.head.removeChild(link);
  203. });
  204. it("validates href when rel attribute is 'shortcut icon' and href is provided", () => {
  205. const link = document.createElement("link");
  206. link.rel = "shortcut icon";
  207. link.href = "/test-shortcut-icon.png";
  208. document.head.appendChild(link);
  209. const notifications: NotificationMessage[] = [
  210. {
  211. nType: "success",
  212. message: "This is a system notification",
  213. system: true,
  214. duration: 3000,
  215. snackbarId: "nId",
  216. },
  217. ];
  218. render(
  219. <SnackbarProvider>
  220. <TaipyNotification notifications={notifications} />
  221. </SnackbarProvider>
  222. );
  223. const linkElement = document.querySelector("link[rel='shortcut icon']");
  224. expect(linkElement?.getAttribute("href")).toBe("/test-shortcut-icon.png");
  225. document.head.removeChild(link);
  226. });
  227. });