зеркало из https://github.com/nextcloud/spreed.git
JS tests for FilePreview component
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
This commit is contained in:
Родитель
8d61c84e2b
Коммит
babb4fa043
|
@ -0,0 +1,519 @@
|
|||
import Vuex from 'vuex'
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import storeConfig from '../../../../../store/storeConfig'
|
||||
import { imagePath, generateRemoteUrl } from '@nextcloud/router'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import PlayCircleOutline from 'vue-material-design-icons/PlayCircleOutline'
|
||||
|
||||
import FilePreview from './FilePreview'
|
||||
|
||||
jest.mock('@nextcloud/initial-state', () => ({
|
||||
loadState: jest.fn(),
|
||||
}))
|
||||
|
||||
describe('FilePreview.vue', () => {
|
||||
let store
|
||||
let localVue
|
||||
let testStoreConfig
|
||||
let propsData
|
||||
let imageMock
|
||||
let getUserIdMock
|
||||
let oldPixelRatio
|
||||
|
||||
beforeEach(() => {
|
||||
localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
|
||||
oldPixelRatio = window.devicePixelRatio
|
||||
|
||||
testStoreConfig = cloneDeep(storeConfig)
|
||||
getUserIdMock = jest.fn().mockReturnValue('current-user-id')
|
||||
testStoreConfig.modules.actorStore.getters.getUserId = () => getUserIdMock
|
||||
store = new Vuex.Store(testStoreConfig)
|
||||
|
||||
imageMock = {
|
||||
onload: jest.fn(),
|
||||
onerror: jest.fn(),
|
||||
src: null,
|
||||
}
|
||||
jest.spyOn(global, 'Image')
|
||||
.mockImplementation(() => {
|
||||
return imageMock
|
||||
})
|
||||
|
||||
propsData = {
|
||||
id: '123',
|
||||
name: 'test.jpg',
|
||||
path: 'path/to/test.jpg',
|
||||
size: 128,
|
||||
mimetype: 'image/jpeg',
|
||||
previewAvailable: 'yes',
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
window.devicePixelRatio = oldPixelRatio
|
||||
})
|
||||
|
||||
function parseRelativeUrl(url) {
|
||||
return new URL('https://localhost' + url)
|
||||
}
|
||||
|
||||
describe('file preview rendering', () => {
|
||||
test('renders file preview', async() => {
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = parseRelativeUrl(wrapper.find('img').attributes('src'))
|
||||
expect(imageUrl.pathname).toBe('/nc-webroot/core/preview')
|
||||
expect(imageUrl.searchParams.get('fileId')).toBe('123')
|
||||
expect(imageUrl.searchParams.get('x')).toBe('-1')
|
||||
expect(imageUrl.searchParams.get('y')).toBe('384')
|
||||
expect(imageUrl.searchParams.get('a')).toBe('1')
|
||||
|
||||
expect(wrapper.find('.loading').exists()).toBe(false)
|
||||
})
|
||||
|
||||
test('renders file preview for guests', async() => {
|
||||
propsData.link = 'https://localhost/nc-webroot/s/xtokenx'
|
||||
getUserIdMock.mockClear().mockReturnValue(null)
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = parseRelativeUrl(wrapper.find('img').attributes('src'))
|
||||
expect(imageUrl.pathname).toBe('/nc-webroot/apps/files_sharing/publicpreview/xtokenx')
|
||||
expect(imageUrl.searchParams.has('fileId')).toBe(false)
|
||||
expect(imageUrl.searchParams.get('x')).toBe('-1')
|
||||
expect(imageUrl.searchParams.get('y')).toBe('384')
|
||||
expect(imageUrl.searchParams.get('a')).toBe('1')
|
||||
|
||||
expect(wrapper.find('.loading').exists()).toBe(false)
|
||||
})
|
||||
|
||||
test('calculates preview size based on window pixel ratio', async() => {
|
||||
window.devicePixelRatio = 1.5
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = parseRelativeUrl(wrapper.find('img').attributes('src'))
|
||||
expect(imageUrl.searchParams.get('y')).toBe('576')
|
||||
})
|
||||
|
||||
test('renders small previews when requested', async() => {
|
||||
propsData.smallPreview = true
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = parseRelativeUrl(wrapper.find('img').attributes('src'))
|
||||
expect(imageUrl.searchParams.get('y')).toBe('32')
|
||||
})
|
||||
|
||||
describe('uploading', () => {
|
||||
let uploadProgressMock
|
||||
|
||||
beforeEach(() => {
|
||||
uploadProgressMock = jest.fn()
|
||||
testStoreConfig.modules.fileUploadStore.getters.uploadProgress = () => uploadProgressMock
|
||||
store = new Vuex.Store(testStoreConfig)
|
||||
})
|
||||
|
||||
test('renders progress bar while uploading', async() => {
|
||||
propsData.id = 'temp-123'
|
||||
propsData.index = 'index-1'
|
||||
propsData.uploadId = 1000
|
||||
propsData.localUrl = 'blob:XYZ'
|
||||
|
||||
uploadProgressMock.mockReturnValue(85)
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('DIV')
|
||||
expect(wrapper.find('img').attributes('src')).toBe('blob:XYZ')
|
||||
|
||||
const progressEl = wrapper.findComponent({ name: 'ProgressBar' })
|
||||
expect(progressEl.exists()).toBe(true)
|
||||
expect(progressEl.props('value')).toBe(85)
|
||||
|
||||
expect(uploadProgressMock).toHaveBeenCalledWith(1000, 'index-1')
|
||||
})
|
||||
})
|
||||
|
||||
test('renders spinner while loading', () => {
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.find('.loading').exists()).toBe(true)
|
||||
})
|
||||
|
||||
test('renders default mime icon on load error', async() => {
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onerror()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = wrapper.find('img').attributes('src')
|
||||
expect(imageUrl).toBe(imagePath('core', 'filetypes/file'))
|
||||
})
|
||||
|
||||
test('renders generic mime type icon for unknown mime types', async() => {
|
||||
propsData.previewAvailable = 'no'
|
||||
OC.MimeType.getIconUrl.mockReturnValueOnce(imagePath('core', 'image/jpeg'))
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = wrapper.find('img').attributes('src')
|
||||
expect(imageUrl).toBe(imagePath('core', 'image/jpeg'))
|
||||
|
||||
expect(OC.MimeType.getIconUrl).toHaveBeenCalledWith('image/jpeg')
|
||||
})
|
||||
|
||||
describe('gif rendering', () => {
|
||||
beforeEach(() => {
|
||||
propsData.mimetype = 'image/gif'
|
||||
propsData.name = 'test.gif'
|
||||
propsData.path = 'path/to/test.gif'
|
||||
|
||||
loadState.mockImplementation((app, key) => {
|
||||
if (app === 'core' && key === 'capabilities') {
|
||||
return {
|
||||
spreed: {
|
||||
config: {
|
||||
previews: {
|
||||
'max-gif-size': 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
})
|
||||
test('directly renders small GIF files', async() => {
|
||||
propsData.size = 128
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
expect(wrapper.find('img').attributes('src'))
|
||||
.toBe(generateRemoteUrl('dav/files/current-user-id/path/to/test.gif'))
|
||||
})
|
||||
|
||||
test('directly renders small GIF files (absolute path)', async() => {
|
||||
propsData.size = 128
|
||||
propsData.path = '/path/to/test.gif'
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
expect(wrapper.find('img').attributes('src'))
|
||||
.toBe(generateRemoteUrl('dav/files/current-user-id/path/to/test.gif'))
|
||||
})
|
||||
|
||||
test('directly renders small GIF files for guests', async() => {
|
||||
propsData.size = 128
|
||||
propsData.link = 'https://localhost/nc-webroot/s/xtokenx'
|
||||
getUserIdMock.mockClear().mockReturnValue(null)
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
expect(wrapper.find('img').attributes('src'))
|
||||
.toBe(propsData.link + '/download/test.gif')
|
||||
})
|
||||
|
||||
test('renders static preview for big GIF files', async() => {
|
||||
// bigger than max from capability
|
||||
propsData.size = 2048
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('A')
|
||||
const imageUrl = parseRelativeUrl(wrapper.find('img').attributes('src'))
|
||||
expect(imageUrl.pathname).toBe('/nc-webroot/core/preview')
|
||||
expect(imageUrl.searchParams.get('fileId')).toBe('123')
|
||||
expect(imageUrl.searchParams.get('x')).toBe('-1')
|
||||
expect(imageUrl.searchParams.get('y')).toBe('384')
|
||||
})
|
||||
})
|
||||
|
||||
describe('triggering viewer', () => {
|
||||
let oldViewer
|
||||
let oldFiles
|
||||
let getSidebarStatusMock
|
||||
|
||||
beforeEach(() => {
|
||||
oldViewer = OCA.Viewer
|
||||
oldFiles = OCA.Files
|
||||
|
||||
OCA.Files = {
|
||||
Sidebar: {
|
||||
state: {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
getSidebarStatusMock = jest.fn().mockReturnValue(true)
|
||||
testStoreConfig.modules.sidebarStore.getters.getSidebarStatus = getSidebarStatusMock
|
||||
store = new Vuex.Store(testStoreConfig)
|
||||
})
|
||||
afterEach(() => {
|
||||
if (oldViewer) {
|
||||
OCA.Viewer = oldViewer
|
||||
} else {
|
||||
delete OCA.Viewer
|
||||
}
|
||||
|
||||
if (oldFiles) {
|
||||
OCA.Files = oldFiles
|
||||
} else {
|
||||
delete OCA.Files
|
||||
}
|
||||
})
|
||||
|
||||
test('opens viewer when clicking if viewer available', async() => {
|
||||
OCA.Viewer = {
|
||||
open: jest.fn(),
|
||||
availableHandlers: [{
|
||||
mimes: ['image/png', 'image/jpeg'],
|
||||
}],
|
||||
}
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
await wrapper.find('a').trigger('click')
|
||||
|
||||
expect(OCA.Viewer.open).toHaveBeenCalledWith({
|
||||
list: [{
|
||||
basename: 'test.jpg',
|
||||
fileid: 123,
|
||||
filename: '/path/to/test.jpg',
|
||||
hasPreview: true,
|
||||
mime: 'image/jpeg',
|
||||
}],
|
||||
path: '/path/to/test.jpg',
|
||||
})
|
||||
|
||||
expect(OCA.Files.Sidebar.state.file).toBe('/path/to/test.jpg')
|
||||
})
|
||||
|
||||
test('does not open viewer when clicking if no mime handler available', async() => {
|
||||
OCA.Viewer = {
|
||||
open: jest.fn(),
|
||||
availableHandlers: [{
|
||||
mimes: ['image/png'],
|
||||
}],
|
||||
}
|
||||
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
await wrapper.find('a').trigger('click')
|
||||
|
||||
expect(OCA.Viewer.open).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('does not open viewer when clicking if viewer is not available', async() => {
|
||||
delete OCA.Viewer
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
// no error
|
||||
await wrapper.find('a').trigger('click')
|
||||
})
|
||||
|
||||
describe('play icon for video', () => {
|
||||
beforeEach(() => {
|
||||
propsData.mimetype = 'video/mp4'
|
||||
propsData.name = 'test.mp4'
|
||||
propsData.path = 'path/to/test.mp4'
|
||||
|
||||
// viewer needs to be available
|
||||
OCA.Viewer = {
|
||||
open: jest.fn(),
|
||||
availableHandlers: [{
|
||||
mimes: ['video/mp4', 'image/jpeg', 'image/png', 'image/gif'],
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
async function testPlayButtonVisible(visible) {
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
const buttonEl = wrapper.findComponent(PlayCircleOutline)
|
||||
expect(buttonEl.exists()).toBe(visible)
|
||||
}
|
||||
|
||||
test('renders play icon for video previews', async() => {
|
||||
await testPlayButtonVisible(true)
|
||||
})
|
||||
|
||||
test('does not render play icon for direct renders', async() => {
|
||||
// gif is directly rendered
|
||||
propsData.mimetype = 'image/gif'
|
||||
propsData.name = 'test.gif'
|
||||
propsData.path = 'path/to/test.gif'
|
||||
|
||||
await testPlayButtonVisible(false)
|
||||
})
|
||||
|
||||
test('render play icon gif previews with big size', async() => {
|
||||
// gif is directly rendered
|
||||
propsData.mimetype = 'image/gif'
|
||||
propsData.name = 'test.gif'
|
||||
propsData.path = 'path/to/test.gif'
|
||||
propsData.size = 10000000 // bigger than default max
|
||||
|
||||
await testPlayButtonVisible(true)
|
||||
})
|
||||
|
||||
test('does not render play icon for small previews', async() => {
|
||||
propsData.smallPreview = true
|
||||
await testPlayButtonVisible(false)
|
||||
})
|
||||
|
||||
test('does not render play icon for failed videos', async() => {
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onerror()
|
||||
|
||||
const buttonEl = wrapper.findComponent(PlayCircleOutline)
|
||||
expect(buttonEl.exists()).toBe(false)
|
||||
})
|
||||
|
||||
test('does not render play icon if viewer not available', async() => {
|
||||
delete OCA.Viewer
|
||||
await testPlayButtonVisible(false)
|
||||
})
|
||||
|
||||
test('does not render play icon for non-videos', async() => {
|
||||
// viewer supported, but not a video
|
||||
propsData.mimetype = 'image/png'
|
||||
propsData.name = 'test.png'
|
||||
propsData.path = 'path/to/test.png'
|
||||
await testPlayButtonVisible(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('in upload editor', () => {
|
||||
beforeEach(() => {
|
||||
propsData.isUploadEditor = true
|
||||
})
|
||||
test('emits event when clicking remove button when inside upload editor', async() => {
|
||||
const wrapper = shallowMount(FilePreview, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: propsData,
|
||||
})
|
||||
|
||||
await imageMock.onload()
|
||||
|
||||
expect(wrapper.element.tagName).toBe('DIV')
|
||||
await wrapper.find('button').trigger('click')
|
||||
|
||||
expect(wrapper.emitted()['remove-file']).toStrictEqual([['123']])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -99,34 +99,54 @@ export default {
|
|||
},
|
||||
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* File id
|
||||
*/
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* File name
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* File path relative to the user's home storage,
|
||||
* or link share root, includes the file name.
|
||||
*/
|
||||
path: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* File size in bytes
|
||||
*/
|
||||
size: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
/**
|
||||
* Download link
|
||||
*/
|
||||
link: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* Mime type
|
||||
*/
|
||||
mimetype: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* Whether a preview is available, string "yes" for yes
|
||||
* otherwise the string "no"
|
||||
*/
|
||||
// FIXME: use booleans here
|
||||
previewAvailable: {
|
||||
type: String,
|
||||
default: 'no',
|
||||
|
@ -138,25 +158,38 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// In case this component is used to display a file that is being uploaded
|
||||
// this parameter is used to access the file upload status in the store
|
||||
/**
|
||||
* Upload id from the file upload store.
|
||||
*
|
||||
* In case this component is used to display a file that is being uploaded
|
||||
* this parameter is used to access the file upload status in the store
|
||||
*/
|
||||
uploadId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
// In case this component is used to display a file that is being uploaded
|
||||
// this parameter is used to access the file upload status in the store
|
||||
/**
|
||||
* File upload index from the file upload store.
|
||||
*
|
||||
* In case this component is used to display a file that is being uploaded
|
||||
* this parameter is used to access the file upload status in the store
|
||||
*/
|
||||
index: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// True if this component is used in the upload editor
|
||||
/**
|
||||
* Whether the container is the upload editor.
|
||||
* True if this component is used in the upload editor.
|
||||
*/
|
||||
// FIXME: file-preview should be encapsulated and not be aware of its surroundings
|
||||
isUploadEditor: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// The link to the file for displaying it in the preview
|
||||
/**
|
||||
* The link to the file for displaying it in the preview
|
||||
*/
|
||||
localUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -323,6 +356,7 @@ export default {
|
|||
return this.$store.getters.uploadProgress(this.uploadId, this.index)
|
||||
}
|
||||
}
|
||||
// likely never reached
|
||||
return 0
|
||||
},
|
||||
hasTemporaryImageUrl() {
|
||||
|
@ -334,7 +368,7 @@ export default {
|
|||
},
|
||||
|
||||
removeAriaLabel() {
|
||||
return t('spreed', 'Remove' + this.name)
|
||||
return t('spreed', 'Remove {fileName}', { fileName: this.name })
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -27,6 +27,9 @@ import Vue from 'vue'
|
|||
global.OC = {
|
||||
requestToken: '123',
|
||||
webroot: '/nc-webroot',
|
||||
coreApps: [
|
||||
'core',
|
||||
],
|
||||
config: {
|
||||
modRewriteWorking: true,
|
||||
},
|
||||
|
@ -41,6 +44,10 @@ global.OC = {
|
|||
getLocale() {
|
||||
return 'en_GB'
|
||||
},
|
||||
|
||||
MimeType: {
|
||||
getIconUrl: jest.fn(),
|
||||
},
|
||||
}
|
||||
global.OCA = {
|
||||
Talk: {
|
||||
|
|
Загрузка…
Ссылка в новой задаче