Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | // 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 | |
| 5 | import {assert, expect} from 'chai'; |
| 6 | import {MrAttachment} from './mr-attachment.js'; |
| 7 | import {prpcClient} from 'prpc-client-instance.js'; |
| 8 | import {FILE_DOWNLOAD_WARNING} from 'shared/settings.js'; |
| 9 | |
| 10 | let element; |
| 11 | |
| 12 | describe('mr-attachment', () => { |
| 13 | beforeEach(() => { |
| 14 | element = document.createElement('mr-attachment'); |
| 15 | document.body.appendChild(element); |
| 16 | sinon.stub(prpcClient, 'call').returns(Promise.resolve({})); |
| 17 | }); |
| 18 | |
| 19 | afterEach(() => { |
| 20 | document.body.removeChild(element); |
| 21 | prpcClient.call.restore(); |
| 22 | }); |
| 23 | |
| 24 | it('initializes', () => { |
| 25 | assert.instanceOf(element, MrAttachment); |
| 26 | }); |
| 27 | |
| 28 | it('shows image thumbnail', async () => { |
| 29 | element.attachment = { |
| 30 | thumbnailUrl: 'thumbnail.jpeg', |
| 31 | contentType: 'image/jpeg', |
| 32 | }; |
| 33 | await element.updateComplete; |
| 34 | const img = element.shadowRoot.querySelector('img'); |
| 35 | assert.isNotNull(img); |
| 36 | assert.isTrue(img.src.endsWith('thumbnail.jpeg')); |
| 37 | }); |
| 38 | |
| 39 | it('shows video thumbnail', async () => { |
| 40 | element.attachment = { |
| 41 | viewUrl: 'video.mp4', |
| 42 | contentType: 'video/mpeg', |
| 43 | }; |
| 44 | await element.updateComplete; |
| 45 | const video = element.shadowRoot.querySelector('video'); |
| 46 | assert.isNotNull(video); |
| 47 | assert.isTrue(video.src.endsWith('video.mp4')); |
| 48 | }); |
| 49 | |
| 50 | it('does not show image thumbnail if deleted', async () => { |
| 51 | element.attachment = { |
| 52 | thumbnailUrl: 'thumbnail.jpeg', |
| 53 | contentType: 'image/jpeg', |
| 54 | isDeleted: true, |
| 55 | }; |
| 56 | await element.updateComplete; |
| 57 | const img = element.shadowRoot.querySelector('img'); |
| 58 | assert.isNull(img); |
| 59 | }); |
| 60 | |
| 61 | it('does not show video thumbnail if deleted', async () => { |
| 62 | element.attachment = { |
| 63 | viewUrl: 'video.mp4', |
| 64 | contentType: 'video/mpeg', |
| 65 | isDeleted: true, |
| 66 | }; |
| 67 | await element.updateComplete; |
| 68 | const video = element.shadowRoot.querySelector('video'); |
| 69 | assert.isNull(video); |
| 70 | }); |
| 71 | |
| 72 | it('deletes attachment', async () => { |
| 73 | prpcClient.call.callsFake(() => Promise.resolve({})); |
| 74 | |
| 75 | element.attachment = { |
| 76 | attachmentId: 67890, |
| 77 | isDeleted: false, |
| 78 | }; |
| 79 | element.canDelete = true; |
| 80 | element.projectName = 'proj'; |
| 81 | element.localId = 1234; |
| 82 | element.sequenceNum = 3; |
| 83 | await element.updateComplete; |
| 84 | |
| 85 | const deleteButton = element.shadowRoot.querySelector('chops-button'); |
| 86 | deleteButton.click(); |
| 87 | |
| 88 | assert.deepEqual(prpcClient.call.getCall(0).args, [ |
| 89 | 'monorail.Issues', 'DeleteAttachment', |
| 90 | { |
| 91 | issueRef: { |
| 92 | projectName: 'proj', |
| 93 | localId: 1234, |
| 94 | }, |
| 95 | sequenceNum: 3, |
| 96 | attachmentId: 67890, |
| 97 | delete: true, |
| 98 | }, |
| 99 | ]); |
| 100 | assert.isTrue(prpcClient.call.calledOnce); |
| 101 | }); |
| 102 | |
| 103 | it('undeletes attachment', async () => { |
| 104 | prpcClient.call.callsFake(() => Promise.resolve({})); |
| 105 | element.attachment = { |
| 106 | attachmentId: 67890, |
| 107 | isDeleted: true, |
| 108 | }; |
| 109 | element.canDelete = true; |
| 110 | element.projectName = 'proj'; |
| 111 | element.localId = 1234; |
| 112 | element.sequenceNum = 3; |
| 113 | await element.updateComplete; |
| 114 | |
| 115 | const deleteButton = element.shadowRoot.querySelector('chops-button'); |
| 116 | deleteButton.click(); |
| 117 | |
| 118 | assert.deepEqual(prpcClient.call.getCall(0).args, [ |
| 119 | 'monorail.Issues', 'DeleteAttachment', |
| 120 | { |
| 121 | issueRef: { |
| 122 | projectName: 'proj', |
| 123 | localId: 1234, |
| 124 | }, |
| 125 | sequenceNum: 3, |
| 126 | attachmentId: 67890, |
| 127 | delete: false, |
| 128 | }, |
| 129 | ]); |
| 130 | assert.isTrue(prpcClient.call.calledOnce); |
| 131 | }); |
| 132 | |
| 133 | it('view link is not displayed if not given', async () => { |
| 134 | element.attachment = {}; |
| 135 | await element.updateComplete; |
| 136 | const viewLink = element.shadowRoot.querySelector('.attachment-view'); |
| 137 | assert.isNull(viewLink); |
| 138 | }); |
| 139 | |
| 140 | it('view link is displayed if given', async () => { |
| 141 | element.attachment = { |
| 142 | viewUrl: 'http://example.com/attachment.foo', |
| 143 | }; |
| 144 | await element.updateComplete; |
| 145 | const viewLink = element.shadowRoot.querySelector('.attachment-view'); |
| 146 | assert.isNotNull(viewLink); |
| 147 | expect(viewLink).to.be.displayed; |
| 148 | assert.equal(viewLink.href, 'http://example.com/attachment.foo'); |
| 149 | }); |
| 150 | |
| 151 | describe('download', () => { |
| 152 | let downloadLink; |
| 153 | |
| 154 | beforeEach(async () => { |
| 155 | sinon.stub(window, 'confirm').returns(false); |
| 156 | |
| 157 | |
| 158 | element.attachment = {}; |
| 159 | await element.updateComplete; |
| 160 | downloadLink = element.shadowRoot.querySelector('.attachment-download'); |
| 161 | // Prevent Karma from opening up new tabs because of simulated link |
| 162 | // clicks. |
| 163 | downloadLink.removeAttribute('target'); |
| 164 | }); |
| 165 | |
| 166 | afterEach(() => { |
| 167 | window.confirm.restore(); |
| 168 | }); |
| 169 | |
| 170 | it('download link is not displayed if not given', async () => { |
| 171 | element.attachment = {}; |
| 172 | await element.updateComplete; |
| 173 | assert.isTrue(downloadLink.hidden); |
| 174 | }); |
| 175 | |
| 176 | it('download link is displayed if given', async () => { |
| 177 | element.attachment = { |
| 178 | downloadUrl: 'http://example.com/attachment.foo', |
| 179 | }; |
| 180 | await element.updateComplete; |
| 181 | const downloadLink = element.shadowRoot.querySelector( |
| 182 | '.attachment-download'); |
| 183 | assert.isFalse(downloadLink.hidden); |
| 184 | expect(downloadLink).to.be.displayed; |
| 185 | assert.equal(downloadLink.href, 'http://example.com/attachment.foo'); |
| 186 | }); |
| 187 | |
| 188 | it('download allows recognized file extension and type', async () => { |
| 189 | element.attachment = { |
| 190 | contentType: 'image/png', |
| 191 | filename: 'not-a-virus.png', |
| 192 | downloadUrl: '#', |
| 193 | }; |
| 194 | await element.updateComplete; |
| 195 | |
| 196 | downloadLink.click(); |
| 197 | |
| 198 | sinon.assert.notCalled(window.confirm); |
| 199 | }); |
| 200 | |
| 201 | it('file extension matching is case insensitive', async () => { |
| 202 | element.attachment = { |
| 203 | contentType: 'image/png', |
| 204 | filename: 'not-a-virus.PNG', |
| 205 | downloadUrl: '#', |
| 206 | }; |
| 207 | await element.updateComplete; |
| 208 | |
| 209 | downloadLink.click(); |
| 210 | |
| 211 | sinon.assert.notCalled(window.confirm); |
| 212 | }); |
| 213 | |
| 214 | it('download warns on unrecognized file extension and type', async () => { |
| 215 | element.attachment = { |
| 216 | contentType: 'application/virus', |
| 217 | filename: 'fake-virus.exe', |
| 218 | downloadUrl: '#', |
| 219 | }; |
| 220 | await element.updateComplete; |
| 221 | |
| 222 | downloadLink.click(); |
| 223 | |
| 224 | sinon.assert.calledOnce(window.confirm); |
| 225 | sinon.assert.calledWith(window.confirm, FILE_DOWNLOAD_WARNING); |
| 226 | }); |
| 227 | }); |
| 228 | }); |