blob: 47b953b4122d71324f5a40d5c7f06c37c9dbe71c [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {assert} from 'chai';
6import sinon from 'sinon';
7import {MrApp} from './mr-app.js';
8import {store, resetState} from 'reducers/base.js';
9import {select} from 'reducers/projectV0.js';
10
11let element;
12let next;
13
14window.CS_env = {
15 token: 'foo-token',
16};
17
18describe('mr-app', () => {
19 beforeEach(() => {
20 global.ga = sinon.spy();
21 store.dispatch(resetState());
22 element = document.createElement('mr-app');
23 document.body.appendChild(element);
24 element.formsToCheck = [];
25
26 next = sinon.stub();
27 });
28
29 afterEach(() => {
30 global.ga.resetHistory();
31 document.body.removeChild(element);
32 next.reset();
33 });
34
35 it('initializes', () => {
36 assert.instanceOf(element, MrApp);
37 });
38
39 describe('snackbar handling', () => {
40 beforeEach(() => {
41 sinon.spy(store, 'dispatch');
42 });
43
44 afterEach(() => {
45 store.dispatch.restore();
46 });
47
48 it('renders no snackbars', async () => {
49 element._snackbars = [];
50
51 await element.updateComplete;
52
53 const snackbars = element.querySelectorAll('chops-snackbar');
54
55 assert.equal(snackbars.length, 0);
56 });
57
58 it('renders multiple snackbars', async () => {
59 element._snackbars = [
60 {text: 'Snackbar one', id: 'one'},
61 {text: 'Snackbar two', id: 'two'},
62 {text: 'Snackbar three', id: 'thre'},
63 ];
64
65 await element.updateComplete;
66
67 const snackbars = element.querySelectorAll('chops-snackbar');
68
69 assert.equal(snackbars.length, 3);
70
71 assert.include(snackbars[0].textContent, 'Snackbar one');
72 assert.include(snackbars[1].textContent, 'Snackbar two');
73 assert.include(snackbars[2].textContent, 'Snackbar three');
74 });
75
76 it('closing snackbar hides snackbar', async () => {
77 element._snackbars = [
78 {text: 'Snackbar', id: 'one'},
79 ];
80
81 await element.updateComplete;
82
83 const snackbar = element.querySelector('chops-snackbar');
84
85 snackbar.close();
86
87 sinon.assert.calledWith(store.dispatch,
88 {type: 'HIDE_SNACKBAR', id: 'one'});
89 });
90 });
91
92 it('_preRouteHandler calls next()', () => {
93 const ctx = {params: {}};
94
95 element._preRouteHandler(ctx, next);
96
97 sinon.assert.calledOnce(next);
98 });
99
100 it('_preRouteHandler does not call next() on same page nav', () => {
101 element._lastContext = {path: '123'};
102 const ctx = {params: {}, path: '123'};
103
104 element._preRouteHandler(ctx, next);
105
106 assert.isFalse(ctx.handled);
107 sinon.assert.notCalled(next);
108 });
109
110 it('_preRouteHandler parses queryParams', () => {
111 const ctx = {params: {}, querystring: 'q=owner:me&colspec=Summary'};
112 element._preRouteHandler(ctx, next);
113
114 assert.deepEqual(ctx.queryParams, {q: 'owner:me', colspec: 'Summary'});
115 });
116
117 it('_preRouteHandler ignores case for queryParams keys', () => {
118 const ctx = {params: {},
119 querystring: 'Q=owner:me&ColSpeC=Summary&x=owner'};
120 element._preRouteHandler(ctx, next);
121
122 assert.deepEqual(ctx.queryParams, {q: 'owner:me', colspec: 'Summary',
123 x: 'owner'});
124 });
125
126 it('_preRouteHandler ignores case for queryParams keys', () => {
127 const ctx = {params: {},
128 querystring: 'Q=owner:me&ColSpeC=Summary&x=owner'};
129 element._preRouteHandler(ctx, next);
130
131 assert.deepEqual(ctx.queryParams, {q: 'owner:me', colspec: 'Summary',
132 x: 'owner'});
133 });
134
135 it('_postRouteHandler saves ctx.queryParams to Redux', () => {
136 const ctx = {queryParams: {q: '1234'}};
137 element._postRouteHandler(ctx, next);
138
139 assert.deepEqual(element.queryParams, {q: '1234'});
140 });
141
142 it('_postRouteHandler saves ctx to this._lastContext', () => {
143 const ctx = {path: '1234'};
144 element._postRouteHandler(ctx, next);
145
146 assert.deepEqual(element._lastContext, {path: '1234'});
147 });
148
149 describe('scroll to the top on page changes', () => {
150 beforeEach(() => {
151 sinon.stub(window, 'scrollTo');
152 });
153
154 afterEach(() => {
155 window.scrollTo.restore();
156 });
157
158 it('scrolls page to top on initial load', () => {
159 element._lastContext = null;
160 const ctx = {params: {}, path: '1234'};
161 element._postRouteHandler(ctx, next);
162
163 sinon.assert.calledWith(window.scrollTo, 0, 0);
164 });
165
166 it('scrolls page to top on parh change', () => {
167 element._lastContext = {params: {}, pathname: '/list',
168 path: '/list?q=123', querystring: '?q=123', queryParams: {q: '123'}};
169 const ctx = {params: {}, pathname: '/other',
170 path: '/other?q=123', querystring: '?q=123', queryParams: {q: '123'}};
171
172 element._postRouteHandler(ctx, next);
173
174 sinon.assert.calledWith(window.scrollTo, 0, 0);
175 });
176
177 it('does not scroll to top when on the same path', () => {
178 element._lastContext = {pathname: '/list', path: '/list?q=123',
179 querystring: '?a=123', queryParams: {a: '123'}};
180 const ctx = {pathname: '/list', path: '/list?q=456',
181 querystring: '?a=456', queryParams: {a: '456'}};
182
183 element._postRouteHandler(ctx, next);
184
185 sinon.assert.notCalled(window.scrollTo);
186 });
187
188 it('scrolls to the top on same path when q param changes', () => {
189 element._lastContext = {pathname: '/list', path: '/list?q=123',
190 querystring: '?q=123', queryParams: {q: '123'}};
191 const ctx = {pathname: '/list', path: '/list?q=456',
192 querystring: '?q=456', queryParams: {q: '456'}};
193
194 element._postRouteHandler(ctx, next);
195
196 sinon.assert.calledWith(window.scrollTo, 0, 0);
197 });
198 });
199
200
201 it('_postRouteHandler does not call next', () => {
202 const ctx = {path: '1234'};
203 element._postRouteHandler(ctx, next);
204
205 sinon.assert.notCalled(next);
206 });
207
208 it('_loadIssuePage loads issue page', async () => {
209 await element._loadIssuePage({
210 queryParams: {id: '234'},
211 params: {project: 'chromium'},
212 }, next);
213 await element.updateComplete;
214
215 // Check that only one page element is rendering at a time.
216 const main = element.querySelector('main');
217 assert.equal(main.children.length, 1);
218
219 const issuePage = element.querySelector('mr-issue-page');
220 assert.isDefined(issuePage, 'issue page is defined');
221 assert.equal(issuePage.issueRef.projectName, 'chromium');
222 assert.equal(issuePage.issueRef.localId, 234);
223 });
224
225 it('_loadListPage loads list page', async () => {
226 await element._loadListPage({
227 params: {project: 'chromium'},
228 }, next);
229 await element.updateComplete;
230
231 // Check that only one page element is rendering at a time.
232 const main = element.querySelector('main');
233 assert.equal(main.children.length, 1);
234
235 const listPage = element.querySelector('mr-list-page');
236 assert.isDefined(listPage, 'list page is defined');
237 });
238
239 it('_loadListPage loads grid page', async () => {
240 element.queryParams = {mode: 'grid'};
241 await element._loadListPage({
242 params: {project: 'chromium'},
243 }, next);
244 await element.updateComplete;
245
246 // Check that only one page element is rendering at a time.
247 const main = element.querySelector('main');
248 assert.equal(main.children.length, 1);
249
250 const gridPage = element.querySelector('mr-grid-page');
251 assert.isDefined(gridPage, 'grid page is defined');
252 });
253
254 describe('_selectProject', () => {
255 beforeEach(() => {
256 sinon.spy(store, 'dispatch');
257 });
258
259 afterEach(() => {
260 store.dispatch.restore();
261 });
262
263 it('selects and fetches project', () => {
264 const projectName = 'chromium';
265 assert.notEqual(store.getState().projectV0.name, projectName);
266
267 element._selectProject(projectName);
268
269 sinon.assert.calledTwice(store.dispatch);
270 });
271
272 it('skips selecting and fetching when project isn\'t changing', () => {
273 const projectName = 'chromium';
274
275 store.dispatch.restore();
276 store.dispatch(select(projectName));
277 sinon.spy(store, 'dispatch');
278
279 assert.equal(store.getState().projectV0.name, projectName);
280
281 element._selectProject(projectName);
282
283 sinon.assert.notCalled(store.dispatch);
284 });
285
286 it('selects without fetching when transitioning to null', () => {
287 const projectName = 'chromium';
288
289 store.dispatch.restore();
290 store.dispatch(select(projectName));
291 sinon.spy(store, 'dispatch');
292
293 assert.equal(store.getState().projectV0.name, projectName);
294
295 element._selectProject(null);
296
297 sinon.assert.calledOnce(store.dispatch);
298 });
299 });
300});