Adrià Vilanova MartÃnez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import {assert} from 'chai'; |
| 6 | import sinon from 'sinon'; |
| 7 | import {MrApp} from './mr-app.js'; |
| 8 | import {store, resetState} from 'reducers/base.js'; |
| 9 | import {select} from 'reducers/projectV0.js'; |
| 10 | |
| 11 | let element; |
| 12 | let next; |
| 13 | |
| 14 | window.CS_env = { |
| 15 | token: 'foo-token', |
| 16 | }; |
| 17 | |
| 18 | describe('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 | }); |