Skip to content

Commit a0ea76b

Browse files
authored
Add 17 test cases covering component rendering, props, interactions, and integration (#1894)
- Test warning icon display with correct IconMap.WARN_OCTAGON - Test message display and translation - Test confirm/cancel button rendering with i18n keys - Test button interactions (onConfirmation callback, modal closing) - Test prop handling (custom modal name, confirm button label) - Test accessibility properties (aria-labels with i18n keys) - Test ModalBase integration and content rendering - Test edge cases (empty confirmBtnLabel fallback) - Test composable integration (useModalHandlers calls) - Use Vue Test Utils with proper component stubs for globally registered components - Follow project testing patterns with i18n key assertions instead of translated text - Mock useModalHandlers composable and verify modal management behavior All tests pass and provide comprehensive coverage of ModalAlert functionality
1 parent 8f0147a commit a0ea76b

File tree

1 file changed

+268
-0
lines changed

1 file changed

+268
-0
lines changed
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+
import { mount } from "@vue/test-utils";
4+
import { createPinia, setActivePinia } from "pinia";
5+
import { describe, it, expect, vi, beforeEach } from "vitest";
6+
7+
import ModalAlert from "../../../app/components/modal/ModalAlert.vue";
8+
import { useModalHandlers } from "../../../app/composables/generic/useModalHandlers";
9+
import { IconMap } from "../../../shared/types/icon-map";
10+
11+
// MARK: Mocks
12+
13+
// Mock the useModalHandlers composable
14+
const mockHandleCloseModal = vi.fn();
15+
vi.mock("../../../app/composables/generic/useModalHandlers", () => ({
16+
useModalHandlers: vi.fn(() => ({
17+
handleCloseModal: mockHandleCloseModal,
18+
})),
19+
}));
20+
21+
// Component stubs for globally registered components
22+
const IconStub = {
23+
name: "Icon",
24+
props: ["name", "size", "color"],
25+
template: '<div data-testid="icon" :data-icon-name="name"></div>',
26+
};
27+
28+
const ModalBaseStub = {
29+
name: "ModalBase",
30+
props: ["modalName"],
31+
template: '<div data-testid="modal-base"><slot /></div>',
32+
};
33+
34+
const BtnActionStub = {
35+
name: "BtnAction",
36+
props: ["ariaLabel", "label", "cta", "fontSize"],
37+
emits: ["click"],
38+
template:
39+
'<button :aria-label="ariaLabel" @click="$emit(\'click\')">{{ label }}</button>',
40+
};
41+
42+
type ModalAlertProps = {
43+
message: string;
44+
onConfirmation?: () => void;
45+
confirmBtnLabel?: string;
46+
name?: string;
47+
};
48+
49+
const createWrapper = (props: Partial<ModalAlertProps> = {}) => {
50+
const pinia = createPinia();
51+
setActivePinia(pinia);
52+
53+
return mount(ModalAlert, {
54+
props: {
55+
message: "Test message",
56+
...props,
57+
},
58+
global: {
59+
plugins: [pinia],
60+
stubs: {
61+
ModalBase: ModalBaseStub,
62+
Icon: IconStub,
63+
BtnAction: BtnActionStub,
64+
},
65+
mocks: {
66+
$t: (key: string) => key,
67+
},
68+
config: {
69+
globalProperties: {
70+
IconMap,
71+
},
72+
},
73+
},
74+
});
75+
};
76+
77+
describe("ModalAlert component", () => {
78+
const mockOnConfirmation = vi.fn();
79+
80+
beforeEach(() => {
81+
vi.clearAllMocks();
82+
});
83+
84+
describe("Component Rendering", () => {
85+
it("displays warning icon with correct properties", () => {
86+
const wrapper = createWrapper();
87+
// Find the Icon component and check it receives the correct name prop
88+
const iconComponent = wrapper.findComponent({ name: "Icon" });
89+
expect(iconComponent.exists()).toBe(true);
90+
expect(iconComponent.props("name")).toBe(IconMap.WARN_OCTAGON);
91+
});
92+
93+
it("displays message", () => {
94+
const wrapper = createWrapper({ message: "Custom message" });
95+
expect(wrapper.text()).toContain("Custom message");
96+
});
97+
98+
it("renders confirm and cancel buttons", () => {
99+
const wrapper = createWrapper();
100+
101+
// Check for confirm button with i18n key
102+
const confirmButton = wrapper.find(
103+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
104+
);
105+
expect(confirmButton.exists()).toBe(true);
106+
expect(confirmButton.text()).toBe("i18n.components.modal_alert.confirm");
107+
108+
// Check for cancel button with i18n key
109+
const cancelButton = wrapper.find(
110+
'[aria-label="i18n.components.modal_alert.cancel_action_aria_label"]'
111+
);
112+
expect(cancelButton.exists()).toBe(true);
113+
expect(cancelButton.text()).toBe("i18n.components.modal_alert.cancel");
114+
});
115+
});
116+
117+
describe("Props and Default Values", () => {
118+
it("uses default modal name when not provided", () => {
119+
createWrapper();
120+
expect(vi.mocked(useModalHandlers)).toHaveBeenCalledWith("ModalAlert");
121+
});
122+
123+
it("uses custom modal name when provided", () => {
124+
createWrapper({ name: "CustomModal" });
125+
expect(vi.mocked(useModalHandlers)).toHaveBeenCalledWith("CustomModal");
126+
});
127+
128+
it("uses default confirm button label", () => {
129+
const wrapper = createWrapper();
130+
const confirmButton = wrapper.find(
131+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
132+
);
133+
expect(confirmButton.text()).toBe("i18n.components.modal_alert.confirm");
134+
});
135+
136+
it("uses custom confirm button label", () => {
137+
const wrapper = createWrapper({
138+
confirmBtnLabel: "Custom Confirm",
139+
});
140+
const confirmButton = wrapper.find(
141+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
142+
);
143+
expect(confirmButton.text()).toBe("Custom Confirm");
144+
});
145+
146+
it("sets correct button accessibility properties", () => {
147+
const wrapper = createWrapper();
148+
149+
const confirmButton = wrapper.find(
150+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
151+
);
152+
const cancelButton = wrapper.find(
153+
'[aria-label="i18n.components.modal_alert.cancel_action_aria_label"]'
154+
);
155+
156+
expect(confirmButton.attributes("aria-label")).toBe(
157+
"i18n.components.modal_alert.confirm_action_aria_label"
158+
);
159+
expect(cancelButton.attributes("aria-label")).toBe(
160+
"i18n.components.modal_alert.cancel_action_aria_label"
161+
);
162+
});
163+
});
164+
165+
describe("Button Interactions", () => {
166+
it("calls onConfirmation and closes modal on confirm", async () => {
167+
const wrapper = createWrapper({
168+
onConfirmation: mockOnConfirmation,
169+
});
170+
171+
const confirmButton = wrapper.find(
172+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
173+
);
174+
await confirmButton.trigger("click");
175+
176+
expect(mockOnConfirmation).toHaveBeenCalledTimes(1);
177+
expect(mockHandleCloseModal).toHaveBeenCalledTimes(1);
178+
});
179+
180+
it("closes modal on cancel without calling onConfirmation", async () => {
181+
const wrapper = createWrapper({
182+
onConfirmation: mockOnConfirmation,
183+
});
184+
185+
const cancelButton = wrapper.find(
186+
'[aria-label="i18n.components.modal_alert.cancel_action_aria_label"]'
187+
);
188+
await cancelButton.trigger("click");
189+
190+
expect(mockOnConfirmation).not.toHaveBeenCalled();
191+
expect(mockHandleCloseModal).toHaveBeenCalledTimes(1);
192+
});
193+
194+
it("works without onConfirmation callback", async () => {
195+
const wrapper = createWrapper();
196+
197+
const confirmButton = wrapper.find(
198+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
199+
);
200+
await confirmButton.trigger("click");
201+
202+
expect(mockHandleCloseModal).toHaveBeenCalledTimes(1);
203+
});
204+
});
205+
206+
describe("ModalBase Integration", () => {
207+
it("passes correct modal name to ModalBase", () => {
208+
const wrapper = createWrapper({ name: "TestModal" });
209+
const modalBase = wrapper.find('[data-testid="modal-base"]');
210+
expect(modalBase.exists()).toBe(true);
211+
});
212+
213+
it("renders content inside ModalBase", () => {
214+
const wrapper = createWrapper({ message: "Test content" });
215+
const modalBase = wrapper.find('[data-testid="modal-base"]');
216+
expect(modalBase.text()).toContain("Test content");
217+
});
218+
});
219+
220+
describe("Layout and Structure", () => {
221+
it("has correct semantic structure", () => {
222+
const wrapper = createWrapper();
223+
224+
// Check that ModalBase exists
225+
const modalBase = wrapper.find('[data-testid="modal-base"]');
226+
expect(modalBase.exists()).toBe(true);
227+
228+
// Check that Icon component exists
229+
const iconComponent = wrapper.findComponent({ name: "Icon" });
230+
expect(iconComponent.exists()).toBe(true);
231+
232+
// Check that buttons exist
233+
const confirmButton = wrapper.find(
234+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
235+
);
236+
const cancelButton = wrapper.find(
237+
'[aria-label="i18n.components.modal_alert.cancel_action_aria_label"]'
238+
);
239+
expect(confirmButton.exists()).toBe(true);
240+
expect(cancelButton.exists()).toBe(true);
241+
242+
// Check that the message is displayed
243+
expect(wrapper.text()).toContain("Test message");
244+
});
245+
});
246+
247+
describe("Edge Cases", () => {
248+
it("handles empty confirmBtnLabel fallback", () => {
249+
const wrapper = createWrapper({ confirmBtnLabel: "" });
250+
const confirmButton = wrapper.find(
251+
'[aria-label="i18n.components.modal_alert.confirm_action_aria_label"]'
252+
);
253+
expect(confirmButton.text()).toBe("i18n.components.modal_alert.confirm");
254+
});
255+
});
256+
257+
describe("Composable Integration", () => {
258+
it("calls useModalHandlers with correct modal name", () => {
259+
createWrapper({ name: "TestModal" });
260+
expect(vi.mocked(useModalHandlers)).toHaveBeenCalledWith("TestModal");
261+
});
262+
263+
it("calls useModalHandlers with default name when none provided", () => {
264+
createWrapper();
265+
expect(vi.mocked(useModalHandlers)).toHaveBeenCalledWith("ModalAlert");
266+
});
267+
});
268+
});

0 commit comments

Comments
 (0)