Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 2b3ec56

Browse files
authored
test(snackbar): Add Jasmine component and util tests (#5490)
1 parent a85a177 commit 2b3ec56

File tree

4 files changed

+519
-476
lines changed

4 files changed

+519
-476
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/**
2+
* @license
3+
* Copyright 2018 Google Inc.
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
import {emitEvent} from '../../../testing/dom/events';
25+
import {createMockFoundation} from '../../../testing/helpers/foundation';
26+
import {numbers, strings} from '../constants';
27+
import {MDCSnackbar, MDCSnackbarFoundation} from '../index';
28+
29+
function getFixture() {
30+
const wrapper = document.createElement('div');
31+
wrapper.innerHTML = `
32+
<div>
33+
<div class="mdc-snackbar">
34+
<div class="mdc-snackbar__surface">
35+
<div class="mdc-snackbar__label"
36+
role="status"
37+
aria-live="polite">Can't send photo. Retry in 5 seconds.</div>
38+
<div class="mdc-snackbar__actions">
39+
<button type="button" class="mdc-button mdc-snackbar__action">Retry</button>
40+
<button class="mdc-icon-button mdc-snackbar__dismiss material-icons" title="Dismiss">close</button>
41+
</div>
42+
</div>
43+
</div>
44+
</div>`;
45+
const el = wrapper.firstElementChild as HTMLElement;
46+
wrapper.removeChild(el);
47+
return el;
48+
}
49+
50+
function setupTest(fixture = getFixture()) {
51+
const root = fixture;
52+
const surface = fixture.querySelector(strings.SURFACE_SELECTOR)!;
53+
const label = fixture.querySelector(strings.LABEL_SELECTOR)!;
54+
const actions = fixture.querySelector('.mdc-snackbar__actions')!;
55+
const actionButton = fixture.querySelector(strings.ACTION_SELECTOR)!;
56+
const actionIcon = fixture.querySelector(strings.DISMISS_SELECTOR)!;
57+
const announce = jasmine.createSpy('announce');
58+
const component = new MDCSnackbar(root, undefined, () => announce);
59+
return {
60+
component,
61+
announce,
62+
root,
63+
surface,
64+
label,
65+
actions,
66+
actionButton,
67+
actionIcon
68+
};
69+
}
70+
71+
function setupTestWithMocks(fixture = getFixture()) {
72+
const root = fixture;
73+
const surface = fixture.querySelector(strings.SURFACE_SELECTOR)!;
74+
const label = fixture.querySelector(strings.LABEL_SELECTOR)!;
75+
const actions = fixture.querySelector('.mdc-snackbar__actions')!;
76+
const actionButton = fixture.querySelector(strings.ACTION_SELECTOR)!;
77+
const actionIcon = fixture.querySelector(strings.DISMISS_SELECTOR)!;
78+
79+
const mockFoundation = createMockFoundation(MDCSnackbarFoundation);
80+
const announce = jasmine.createSpy('announce');
81+
const component = new MDCSnackbar(root, mockFoundation, () => announce);
82+
83+
return {
84+
component,
85+
mockFoundation,
86+
announce,
87+
root,
88+
surface,
89+
label,
90+
actions,
91+
actionButton,
92+
actionIcon
93+
};
94+
}
95+
96+
describe('MDCSnackbar', () => {
97+
it('attachTo returns a component instance', () => {
98+
expect(MDCSnackbar.attachTo(getFixture()))
99+
.toEqual(jasmine.any(MDCSnackbar));
100+
});
101+
102+
it('#initialSyncWithDOM registers click handlers for action button and action icon',
103+
() => {
104+
const {component, mockFoundation, actionButton, actionIcon} =
105+
setupTestWithMocks();
106+
component.open();
107+
emitEvent(actionButton, 'click', {bubbles: true});
108+
expect(mockFoundation.handleActionButtonClick)
109+
.toHaveBeenCalledWith(jasmine.any(Event));
110+
expect(mockFoundation.handleActionButtonClick).toHaveBeenCalledTimes(1);
111+
emitEvent(actionIcon, 'click', {bubbles: true});
112+
expect(mockFoundation.handleActionIconClick)
113+
.toHaveBeenCalledWith(jasmine.any(Event));
114+
expect(mockFoundation.handleActionIconClick).toHaveBeenCalledTimes(1);
115+
component.destroy();
116+
});
117+
118+
it('#initialSyncWithDOM registers keydown handler on the root element',
119+
() => {
120+
const {component, mockFoundation, root} = setupTestWithMocks();
121+
component.open();
122+
emitEvent(root, 'keydown');
123+
expect(mockFoundation.handleKeyDown)
124+
.toHaveBeenCalledWith(jasmine.any(Event));
125+
expect(mockFoundation.handleKeyDown).toHaveBeenCalledTimes(1);
126+
component.destroy();
127+
});
128+
129+
it('#destroy deregisters click handler on the root element', () => {
130+
const {component, mockFoundation, actionButton, actionIcon} =
131+
setupTestWithMocks();
132+
component.open();
133+
component.destroy();
134+
emitEvent(actionButton, 'click', {bubbles: true});
135+
expect(mockFoundation.handleActionButtonClick)
136+
.not.toHaveBeenCalledWith(jasmine.any(Event));
137+
emitEvent(actionIcon, 'click', {bubbles: true});
138+
expect(mockFoundation.handleActionIconClick)
139+
.not.toHaveBeenCalledWith(jasmine.any(Event));
140+
});
141+
142+
it('#destroy deregisters keydown handler on the root element', () => {
143+
const {component, mockFoundation, root} = setupTestWithMocks();
144+
component.open();
145+
component.destroy();
146+
emitEvent(root, 'keydown');
147+
expect(mockFoundation.handleKeyDown)
148+
.not.toHaveBeenCalledWith(jasmine.any(Event));
149+
});
150+
151+
it('clicking on surface does nothing', () => {
152+
const {component, mockFoundation, surface} = setupTestWithMocks();
153+
component.open();
154+
emitEvent(surface, 'click', {bubbles: true});
155+
expect(mockFoundation.handleActionButtonClick)
156+
.not.toHaveBeenCalledWith(jasmine.any(Event));
157+
expect(mockFoundation.handleActionIconClick)
158+
.not.toHaveBeenCalledWith(jasmine.any(Event));
159+
expect(mockFoundation.close).not.toHaveBeenCalledWith(jasmine.anything());
160+
component.destroy();
161+
});
162+
163+
it('#open announces to screen readers', () => {
164+
const {component, announce, label} = setupTest();
165+
166+
component.open();
167+
expect(announce).toHaveBeenCalledWith(label);
168+
expect(announce).toHaveBeenCalledTimes(1);
169+
});
170+
171+
it('#open forwards to MDCSnackbarFoundation#open', () => {
172+
const {component, mockFoundation} = setupTestWithMocks();
173+
174+
component.open();
175+
expect(mockFoundation.open).toHaveBeenCalled();
176+
});
177+
178+
it('#close forwards to MDCSnackbarFoundation#close', () => {
179+
const {component, mockFoundation} = setupTestWithMocks();
180+
const reason = 'reason';
181+
182+
component.open();
183+
component.close(reason);
184+
expect(mockFoundation.close).toHaveBeenCalledWith(reason);
185+
186+
component.close();
187+
expect(mockFoundation.close).toHaveBeenCalledWith('');
188+
});
189+
190+
it('get isOpen forwards to MDCSnackbarFoundation#isOpen', () => {
191+
const {component, mockFoundation} = setupTestWithMocks();
192+
193+
component.isOpen;
194+
expect(mockFoundation.isOpen).toHaveBeenCalled();
195+
});
196+
197+
it('get closeOnEscape forwards to MDCSnackbarFoundation#getCloseOnEscape',
198+
() => {
199+
const {component, mockFoundation} = setupTestWithMocks();
200+
201+
component.closeOnEscape;
202+
expect(mockFoundation.getCloseOnEscape).toHaveBeenCalled();
203+
});
204+
205+
it('set closeOnEscape forwards to MDCSnackbarFoundation#setCloseOnEscape',
206+
() => {
207+
const {component, mockFoundation} = setupTestWithMocks();
208+
209+
component.closeOnEscape = false;
210+
expect(mockFoundation.setCloseOnEscape).toHaveBeenCalledWith(false);
211+
component.closeOnEscape = true;
212+
expect(mockFoundation.setCloseOnEscape).toHaveBeenCalledWith(false);
213+
});
214+
215+
it('get timeoutMs forwards to MDCSnackbarFoundation#getTimeoutMs', () => {
216+
const {component, mockFoundation} = setupTestWithMocks();
217+
218+
component.timeoutMs;
219+
expect(mockFoundation.getTimeoutMs).toHaveBeenCalled();
220+
});
221+
222+
it('set timeoutMs forwards to MDCSnackbarFoundation#setTimeoutMs', () => {
223+
const {component, mockFoundation} = setupTestWithMocks();
224+
225+
component.timeoutMs = numbers.MAX_AUTO_DISMISS_TIMEOUT_MS;
226+
expect(mockFoundation.setTimeoutMs)
227+
.toHaveBeenCalledWith(numbers.MAX_AUTO_DISMISS_TIMEOUT_MS);
228+
});
229+
230+
it('get labelText returns label textContent', () => {
231+
const {component, label} = setupTestWithMocks();
232+
233+
expect(component.labelText).toEqual(label.textContent!);
234+
});
235+
236+
it('set labelText forwards to MDCSnackbarFoundation#setActionButtonText',
237+
() => {
238+
const {component} = setupTestWithMocks();
239+
240+
component.labelText = 'foo';
241+
expect(component.labelText).toEqual('foo');
242+
});
243+
244+
it('get actionButtonText returns button textContent', () => {
245+
const {component, actionButton} = setupTestWithMocks();
246+
247+
expect(component.actionButtonText).toEqual(actionButton.textContent!);
248+
});
249+
250+
it('set actionButtonText forwards to MDCSnackbarFoundation#setActionButtonText',
251+
() => {
252+
const {component} = setupTestWithMocks();
253+
254+
component.actionButtonText = 'foo';
255+
expect(component.actionButtonText).toEqual('foo');
256+
});
257+
258+
it('adapter#addClass adds a class to the root element', () => {
259+
const {root, component} = setupTest();
260+
(component.getDefaultFoundation() as any).adapter_.addClass('foo');
261+
expect(root.classList.contains('foo')).toBe(true);
262+
});
263+
264+
it('adapter#removeClass removes a class from the root element', () => {
265+
const {root, component} = setupTest();
266+
root.classList.add('foo');
267+
(component.getDefaultFoundation() as any).adapter_.removeClass('foo');
268+
expect(root.classList.contains('foo')).toBe(false);
269+
});
270+
271+
it(`adapter#notifyOpening emits ${strings.OPENING_EVENT}`, () => {
272+
const {component} = setupTest();
273+
274+
const handler = jasmine.createSpy('notifyOpeningHandler');
275+
276+
component.listen(strings.OPENING_EVENT, handler);
277+
(component.getDefaultFoundation() as any).adapter_.notifyOpening();
278+
component.unlisten(strings.OPENING_EVENT, handler);
279+
280+
expect(handler).toHaveBeenCalledWith(jasmine.anything());
281+
});
282+
283+
it(`adapter#notifyOpened emits ${strings.OPENED_EVENT}`, () => {
284+
const {component} = setupTest();
285+
286+
const handler = jasmine.createSpy('notifyOpenedHandler');
287+
288+
component.listen(strings.OPENED_EVENT, handler);
289+
(component.getDefaultFoundation() as any).adapter_.notifyOpened();
290+
component.unlisten(strings.OPENED_EVENT, handler);
291+
292+
expect(handler).toHaveBeenCalledWith(jasmine.anything());
293+
});
294+
295+
it(`adapter#notifyClosing emits ${
296+
strings
297+
.CLOSING_EVENT} without action if passed action is empty string`,
298+
() => {
299+
const {component} = setupTest();
300+
301+
const handler = jasmine.createSpy('notifyClosingHandler');
302+
303+
component.listen(strings.CLOSING_EVENT, handler);
304+
(component.getDefaultFoundation() as any).adapter_.notifyClosing('');
305+
component.unlisten(strings.CLOSING_EVENT, handler);
306+
307+
expect(handler).toHaveBeenCalledWith(
308+
jasmine.objectContaining({detail: {}}));
309+
});
310+
311+
it(`adapter#notifyClosing emits ${strings.CLOSING_EVENT} with reason`, () => {
312+
const {component} = setupTest();
313+
const reason = 'reason';
314+
315+
const handler = jasmine.createSpy('notifyClosingHandler');
316+
317+
component.listen(strings.CLOSING_EVENT, handler);
318+
(component.getDefaultFoundation() as any).adapter_.notifyClosing(reason);
319+
component.unlisten(strings.CLOSING_EVENT, handler);
320+
321+
expect(handler).toHaveBeenCalledWith(
322+
jasmine.objectContaining({detail: {reason}}));
323+
});
324+
325+
it(`adapter#notifyClosed emits ${
326+
strings.CLOSED_EVENT} without reason if passed reason is empty string`,
327+
() => {
328+
const {component} = setupTest();
329+
330+
const handler = jasmine.createSpy('notifyClosedHandler');
331+
332+
component.listen(strings.CLOSED_EVENT, handler);
333+
(component.getDefaultFoundation() as any).adapter_.notifyClosed('');
334+
component.unlisten(strings.CLOSED_EVENT, handler);
335+
336+
expect(handler).toHaveBeenCalledWith(
337+
jasmine.objectContaining({detail: {}}));
338+
});
339+
340+
it(`adapter#notifyClosed emits ${strings.CLOSED_EVENT} with reason`, () => {
341+
const {component} = setupTest();
342+
const reason = 'reason';
343+
344+
const handler = jasmine.createSpy('notifyClosedHandler');
345+
346+
component.listen(strings.CLOSED_EVENT, handler);
347+
(component.getDefaultFoundation() as any).adapter_.notifyClosed(reason);
348+
component.unlisten(strings.CLOSED_EVENT, handler);
349+
350+
expect(handler).toHaveBeenCalledWith(
351+
jasmine.objectContaining({detail: {reason}}));
352+
});
353+
});

0 commit comments

Comments
 (0)