theme_toggle_test.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import { act, assertEquals, cleanup, fireEvent, render } from "./setup.ts";
  2. import ThemeToggle from "../../islands/ThemeToggle.tsx";
  3. function resetState() {
  4. document.documentElement.classList.remove("dark");
  5. localStorage.removeItem("theme");
  6. }
  7. Deno.test({
  8. name: "ThemeToggle - renders moon icon in light mode by default",
  9. fn() {
  10. resetState();
  11. const { container } = render(<ThemeToggle />);
  12. const icon = container.querySelector("i")!;
  13. assertEquals(icon.classList.contains("bi-moon-stars"), true);
  14. assertEquals(icon.classList.contains("bi-sun"), false);
  15. cleanup();
  16. },
  17. sanitizeResources: false,
  18. sanitizeOps: false,
  19. });
  20. Deno.test({
  21. name: "ThemeToggle - renders sun icon when dark class is present",
  22. fn() {
  23. resetState();
  24. document.documentElement.classList.add("dark");
  25. let container: HTMLElement;
  26. act(() => {
  27. ({ container } = render(<ThemeToggle />));
  28. });
  29. const icon = container!.querySelector("i")!;
  30. assertEquals(icon.classList.contains("bi-sun"), true);
  31. assertEquals(icon.classList.contains("bi-moon-stars"), false);
  32. cleanup();
  33. resetState();
  34. },
  35. sanitizeResources: false,
  36. sanitizeOps: false,
  37. });
  38. Deno.test({
  39. name: "ThemeToggle - click adds dark class to documentElement",
  40. fn() {
  41. resetState();
  42. const { container } = render(<ThemeToggle />);
  43. const icon = container.querySelector("i")!;
  44. act(() => {
  45. fireEvent.click(icon);
  46. });
  47. assertEquals(document.documentElement.classList.contains("dark"), true);
  48. cleanup();
  49. resetState();
  50. },
  51. sanitizeResources: false,
  52. sanitizeOps: false,
  53. });
  54. Deno.test({
  55. name: "ThemeToggle - click toggles icon from moon to sun",
  56. fn() {
  57. resetState();
  58. const { container } = render(<ThemeToggle />);
  59. const icon = container.querySelector("i")!;
  60. assertEquals(icon.classList.contains("bi-moon-stars"), true);
  61. act(() => {
  62. fireEvent.click(icon);
  63. });
  64. const updatedIcon = container.querySelector("i")!;
  65. assertEquals(updatedIcon.classList.contains("bi-sun"), true);
  66. assertEquals(updatedIcon.classList.contains("bi-moon-stars"), false);
  67. cleanup();
  68. resetState();
  69. },
  70. sanitizeResources: false,
  71. sanitizeOps: false,
  72. });
  73. Deno.test({
  74. name: "ThemeToggle - second click removes dark class",
  75. fn() {
  76. resetState();
  77. const { container } = render(<ThemeToggle />);
  78. const icon = container.querySelector("i")!;
  79. act(() => {
  80. fireEvent.click(icon);
  81. });
  82. assertEquals(document.documentElement.classList.contains("dark"), true);
  83. act(() => {
  84. fireEvent.click(container.querySelector("i")!);
  85. });
  86. assertEquals(document.documentElement.classList.contains("dark"), false);
  87. cleanup();
  88. resetState();
  89. },
  90. sanitizeResources: false,
  91. sanitizeOps: false,
  92. });
  93. Deno.test({
  94. name: "ThemeToggle - click persists 'dark' to localStorage",
  95. fn() {
  96. resetState();
  97. const { container } = render(<ThemeToggle />);
  98. act(() => {
  99. fireEvent.click(container.querySelector("i")!);
  100. });
  101. assertEquals(localStorage.getItem("theme"), "dark");
  102. cleanup();
  103. resetState();
  104. },
  105. sanitizeResources: false,
  106. sanitizeOps: false,
  107. });
  108. Deno.test({
  109. name: "ThemeToggle - second click persists 'light' to localStorage",
  110. fn() {
  111. resetState();
  112. const { container } = render(<ThemeToggle />);
  113. act(() => {
  114. fireEvent.click(container.querySelector("i")!);
  115. });
  116. act(() => {
  117. fireEvent.click(container.querySelector("i")!);
  118. });
  119. assertEquals(localStorage.getItem("theme"), "light");
  120. cleanup();
  121. resetState();
  122. },
  123. sanitizeResources: false,
  124. sanitizeOps: false,
  125. });
  126. Deno.test({
  127. name: "ThemeToggle - click dispatches ThemeChange event",
  128. fn() {
  129. resetState();
  130. let eventDetail: boolean | null = null;
  131. const listener = (e: Event) => {
  132. eventDetail = (e as CustomEvent).detail;
  133. };
  134. document.addEventListener("ThemeChange", listener);
  135. const { container } = render(<ThemeToggle />);
  136. act(() => {
  137. fireEvent.click(container.querySelector("i")!);
  138. });
  139. assertEquals(eventDetail, true);
  140. document.removeEventListener("ThemeChange", listener);
  141. cleanup();
  142. resetState();
  143. },
  144. sanitizeResources: false,
  145. sanitizeOps: false,
  146. });
  147. Deno.test({
  148. name: "ThemeToggle - has hover classes for both themes",
  149. fn() {
  150. resetState();
  151. const { container } = render(<ThemeToggle />);
  152. const icon = container.querySelector("i")!;
  153. assertEquals(icon.className.includes("hover:text-blue-600"), true);
  154. assertEquals(icon.className.includes("dark:hover:text-blue-400"), true);
  155. cleanup();
  156. },
  157. sanitizeResources: false,
  158. sanitizeOps: false,
  159. });
  160. Deno.test({
  161. name: "ThemeToggle - has correct title attribute in light mode",
  162. fn() {
  163. resetState();
  164. const { container } = render(<ThemeToggle />);
  165. const icon = container.querySelector("i")!;
  166. assertEquals(icon.getAttribute("title"), "Switch to dark mode");
  167. cleanup();
  168. },
  169. sanitizeResources: false,
  170. sanitizeOps: false,
  171. });
  172. Deno.test({
  173. name: "ThemeToggle - has correct title attribute in dark mode",
  174. fn() {
  175. resetState();
  176. document.documentElement.classList.add("dark");
  177. let container: HTMLElement;
  178. act(() => {
  179. ({ container } = render(<ThemeToggle />));
  180. });
  181. const icon = container!.querySelector("i")!;
  182. assertEquals(icon.getAttribute("title"), "Switch to light mode");
  183. cleanup();
  184. resetState();
  185. },
  186. sanitizeResources: false,
  187. sanitizeOps: false,
  188. });