mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
refactor: move OC.MimeType to src and add vitest unit tests
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
057c0dcc98
commit
2e11b96537
6 changed files with 214 additions and 242 deletions
|
|
@ -1,5 +1,4 @@
|
|||
[
|
||||
"mimetype.js",
|
||||
"mimetypelist.js",
|
||||
"select2-toggleselect.js"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2015 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Namespace to hold functions related to convert mimetype to icons
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
OC.MimeType = {
|
||||
|
||||
/**
|
||||
* Cache that maps mimeTypes to icon urls
|
||||
*/
|
||||
_mimeTypeIcons: {},
|
||||
|
||||
/**
|
||||
* Return the file icon we want to use for the given mimeType.
|
||||
* The file needs to be present in the supplied file list
|
||||
*
|
||||
* @param {string} mimeType The mimeType we want an icon for
|
||||
* @param {Array} files The available icons in this theme
|
||||
* @return {string} The icon to use or null if there is no match
|
||||
*/
|
||||
_getFile: function(mimeType, files) {
|
||||
const icon = mimeType.replace(new RegExp('/', 'g'), '-')
|
||||
|
||||
// Generate path
|
||||
if (mimeType === 'dir' && files.includes('folder')) {
|
||||
return 'folder'
|
||||
} else if (mimeType === 'dir-encrypted' && files.includes('folder-encrypted')) {
|
||||
return 'folder-encrypted'
|
||||
} else if (mimeType === 'dir-shared' && files.includes('folder-shared')) {
|
||||
return 'folder-shared'
|
||||
} else if (mimeType === 'dir-public' && files.includes('folder-public')) {
|
||||
return 'folder-public'
|
||||
} else if ((mimeType === 'dir-external' || mimeType === 'dir-external-root') && files.includes('folder-external')) {
|
||||
return 'folder-external'
|
||||
} else if (files.includes(icon)) {
|
||||
return icon
|
||||
} else if (files.includes(icon.split('-')[0])) {
|
||||
return icon.split('-')[0]
|
||||
} else if (files.includes('file')) {
|
||||
return 'file'
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the url to icon of the given mimeType
|
||||
*
|
||||
* @param {string} mimeType The mimeType to get the icon for
|
||||
* @return {string} Url to the icon for mimeType
|
||||
*/
|
||||
getIconUrl: function(mimeType) {
|
||||
if (typeof mimeType === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
while (mimeType in OC.MimeTypeList.aliases) {
|
||||
mimeType = OC.MimeTypeList.aliases[mimeType]
|
||||
}
|
||||
|
||||
if (mimeType in OC.MimeType._mimeTypeIcons) {
|
||||
return OC.MimeType._mimeTypeIcons[mimeType]
|
||||
}
|
||||
|
||||
// First try to get the correct icon from the current theme
|
||||
let gotIcon = null
|
||||
let path = ''
|
||||
if (OC.theme.folder !== '' && Array.isArray(OC.MimeTypeList.themes[OC.theme.folder])) {
|
||||
path = OC.getRootPath() + '/themes/' + OC.theme.folder + '/core/img/filetypes/'
|
||||
const icon = OC.MimeType._getFile(mimeType, OC.MimeTypeList.themes[OC.theme.folder])
|
||||
|
||||
if (icon !== null) {
|
||||
gotIcon = true
|
||||
path += icon
|
||||
}
|
||||
}
|
||||
if (OCA.Theming && gotIcon === null) {
|
||||
path = OC.generateUrl('/apps/theming/img/core/filetypes/')
|
||||
path += OC.MimeType._getFile(mimeType, OC.MimeTypeList.files)
|
||||
gotIcon = true
|
||||
}
|
||||
|
||||
// If we do not yet have an icon fall back to the default
|
||||
if (gotIcon === null) {
|
||||
path = OC.getRootPath() + '/core/img/filetypes/'
|
||||
path += OC.MimeType._getFile(mimeType, OC.MimeTypeList.files)
|
||||
}
|
||||
|
||||
path += '.svg'
|
||||
|
||||
if (OCA.Theming) {
|
||||
path += '?v=' + OCA.Theming.cacheBuster
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
OC.MimeType._mimeTypeIcons[mimeType] = path
|
||||
return path
|
||||
},
|
||||
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
describe('MimeType tests', function() {
|
||||
let _files
|
||||
let _aliases
|
||||
let _theme
|
||||
|
||||
beforeEach(function() {
|
||||
_files = OC.MimeTypeList.files
|
||||
_aliases = OC.MimeTypeList.aliases
|
||||
_theme = OC.MimeTypeList.themes.abc
|
||||
|
||||
OC.MimeTypeList.files = ['folder', 'folder-shared', 'folder-external', 'foo-bar', 'foo', 'file']
|
||||
OC.MimeTypeList.aliases = { 'app/foobar': 'foo/bar' }
|
||||
OC.MimeTypeList.themes.abc = ['folder']
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
OC.MimeTypeList.files = _files
|
||||
OC.MimeTypeList.aliases = _aliases
|
||||
OC.MimeTypeList.themes.abc = _theme
|
||||
})
|
||||
|
||||
describe('_getFile', function() {
|
||||
it('returns the correct icon for "dir"', function() {
|
||||
const res = OC.MimeType._getFile('dir', OC.MimeTypeList.files)
|
||||
expect(res).toEqual('folder')
|
||||
})
|
||||
|
||||
it('returns the correct icon for "dir-shared"', function() {
|
||||
const res = OC.MimeType._getFile('dir-shared', OC.MimeTypeList.files)
|
||||
expect(res).toEqual('folder-shared')
|
||||
})
|
||||
|
||||
it('returns the correct icon for "dir-external"', function() {
|
||||
const res = OC.MimeType._getFile('dir-external', OC.MimeTypeList.files)
|
||||
expect(res).toEqual('folder-external')
|
||||
})
|
||||
|
||||
it('returns the correct icon for a mimetype for which we have an icon', function() {
|
||||
const res = OC.MimeType._getFile('foo/bar', OC.MimeTypeList.files)
|
||||
expect(res).toEqual('foo-bar')
|
||||
})
|
||||
|
||||
it('returns the correct icon for a mimetype for which we only have a general mimetype icon', function() {
|
||||
const res = OC.MimeType._getFile('foo/baz', OC.MimeTypeList.files)
|
||||
expect(res).toEqual('foo')
|
||||
})
|
||||
|
||||
it('return the file mimetype if we have no matching icon but do have a file icon', function() {
|
||||
const res = OC.MimeType._getFile('foobar', OC.MimeTypeList.files)
|
||||
expect(res).toEqual('file')
|
||||
})
|
||||
|
||||
it('return null if we do not have a matching icon', function() {
|
||||
const res = OC.MimeType._getFile('xyz', [])
|
||||
expect(res).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getIconUrl', function() {
|
||||
describe('no theme', function() {
|
||||
let _themeFolder
|
||||
|
||||
beforeEach(function() {
|
||||
_themeFolder = OC.theme.folder
|
||||
OC.theme.folder = ''
|
||||
// Clear mimetypeIcons caches
|
||||
OC.MimeType._mimeTypeIcons = {}
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
OC.theme.folder = _themeFolder
|
||||
})
|
||||
|
||||
it('return undefined if the an icon for undefined is requested', function() {
|
||||
const res = OC.MimeType.getIconUrl(undefined)
|
||||
expect(res).toEqual(undefined)
|
||||
})
|
||||
|
||||
it('return the url for the mimetype file', function() {
|
||||
const res = OC.MimeType.getIconUrl('file')
|
||||
expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/file.svg')
|
||||
})
|
||||
|
||||
it('test if the cache works correctly', function() {
|
||||
OC.MimeType._mimeTypeIcons = {}
|
||||
expect(Object.keys(OC.MimeType._mimeTypeIcons).length).toEqual(0)
|
||||
|
||||
let res = OC.MimeType.getIconUrl('dir')
|
||||
expect(Object.keys(OC.MimeType._mimeTypeIcons).length).toEqual(1)
|
||||
expect(OC.MimeType._mimeTypeIcons.dir).toEqual(res)
|
||||
|
||||
res = OC.MimeType.getIconUrl('dir-shared')
|
||||
expect(Object.keys(OC.MimeType._mimeTypeIcons).length).toEqual(2)
|
||||
expect(OC.MimeType._mimeTypeIcons['dir-shared']).toEqual(res)
|
||||
})
|
||||
|
||||
it('test if alaiases are converted correctly', function() {
|
||||
const res = OC.MimeType.getIconUrl('app/foobar')
|
||||
expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/foo-bar.svg')
|
||||
expect(OC.MimeType._mimeTypeIcons['foo/bar']).toEqual(res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('themes', function() {
|
||||
let _themeFolder
|
||||
|
||||
beforeEach(function() {
|
||||
_themeFolder = OC.theme.folder
|
||||
OC.theme.folder = 'abc'
|
||||
// Clear mimetypeIcons caches
|
||||
OC.MimeType._mimeTypeIcons = {}
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
OC.theme.folder = _themeFolder
|
||||
})
|
||||
|
||||
it('test if theme path is used if a theme icon is availble', function() {
|
||||
const res = OC.MimeType.getIconUrl('dir')
|
||||
expect(res).toEqual(OC.getRootPath() + '/themes/abc/core/img/filetypes/folder.svg')
|
||||
})
|
||||
|
||||
it('test if we fallback to the default theme if no icon is available in the theme', function() {
|
||||
const res = OC.MimeType.getIconUrl('dir-shared')
|
||||
expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/folder-shared.svg')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -63,6 +63,7 @@ import {
|
|||
showMenu,
|
||||
unregisterMenu,
|
||||
} from './menu.js'
|
||||
import * as MimeType from './mimeType.js'
|
||||
import msg from './msg.js'
|
||||
import { redirect, reload } from './navigation.js'
|
||||
import Notification from './notification.js'
|
||||
|
|
@ -127,6 +128,7 @@ export default {
|
|||
currentUser,
|
||||
dialogs: Dialogs,
|
||||
EventSource,
|
||||
MimeType,
|
||||
/**
|
||||
* Returns the currently logged in user or null if there is no logged in
|
||||
* user (public page mode)
|
||||
|
|
|
|||
94
core/src/OC/mimeType.js
Normal file
94
core/src/OC/mimeType.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
const iconCache = new Map()
|
||||
|
||||
/**
|
||||
* Return the url to icon of the given mimeType
|
||||
*
|
||||
* @param {string} mimeType The mimeType to get the icon for
|
||||
* @return {string} Url to the icon for mimeType
|
||||
*/
|
||||
export function getIconUrl(mimeType) {
|
||||
if (typeof mimeType === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
while (mimeType in window.OC.MimeTypeList.aliases) {
|
||||
mimeType = window.OC.MimeTypeList.aliases[mimeType]
|
||||
}
|
||||
|
||||
if (!iconCache.has(mimeType)) {
|
||||
// First try to get the correct icon from the current theme
|
||||
let gotIcon = null
|
||||
let path = ''
|
||||
if (OC.theme.folder !== '' && Array.isArray(OC.MimeTypeList.themes[OC.theme.folder])) {
|
||||
path = generateUrl('/themes/' + window.OC.theme.folder + '/core/img/filetypes/')
|
||||
const icon = getMimeTypeIcon(mimeType, window.OC.MimeTypeList.themes[OC.theme.folder])
|
||||
|
||||
if (icon !== null) {
|
||||
gotIcon = true
|
||||
path += icon
|
||||
}
|
||||
}
|
||||
if (window.OCA.Theming && gotIcon === null) {
|
||||
path = generateUrl('/apps/theming/img/core/filetypes/')
|
||||
path += getMimeTypeIcon(mimeType, window.OC.MimeTypeList.files)
|
||||
gotIcon = true
|
||||
}
|
||||
|
||||
// If we do not yet have an icon fall back to the default
|
||||
if (gotIcon === null) {
|
||||
path = generateUrl('/core/img/filetypes/')
|
||||
path += getMimeTypeIcon(mimeType, window.OC.MimeTypeList.files)
|
||||
}
|
||||
|
||||
path += '.svg'
|
||||
|
||||
if (window.OCA.Theming) {
|
||||
path += '?v=' + window.OCA.Theming.cacheBuster
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
iconCache.set(mimeType, path)
|
||||
}
|
||||
|
||||
return iconCache.get(mimeType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file icon we want to use for the given mimeType.
|
||||
* The file needs to be present in the supplied file list
|
||||
*
|
||||
* @param {string} mimeType The mimeType we want an icon for
|
||||
* @param {string[]} files The available icons in this theme
|
||||
* @return {string | null} The icon to use or null if there is no match
|
||||
*/
|
||||
function getMimeTypeIcon(mimeType, files) {
|
||||
const icon = mimeType.replace(new RegExp('/', 'g'), '-')
|
||||
|
||||
// Generate path
|
||||
if (mimeType === 'dir' && files.includes('folder')) {
|
||||
return 'folder'
|
||||
} else if (mimeType === 'dir-encrypted' && files.includes('folder-encrypted')) {
|
||||
return 'folder-encrypted'
|
||||
} else if (mimeType === 'dir-shared' && files.includes('folder-shared')) {
|
||||
return 'folder-shared'
|
||||
} else if (mimeType === 'dir-public' && files.includes('folder-public')) {
|
||||
return 'folder-public'
|
||||
} else if ((mimeType === 'dir-external' || mimeType === 'dir-external-root') && files.includes('folder-external')) {
|
||||
return 'folder-external'
|
||||
} else if (files.includes(icon)) {
|
||||
return icon
|
||||
} else if (files.includes(icon.split('-')[0])) {
|
||||
return icon.split('-')[0]
|
||||
} else if (files.includes('file')) {
|
||||
return 'file'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
118
core/src/tests/OC/mimeType.spec.ts
Normal file
118
core/src/tests/OC/mimeType.spec.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { join } from 'node:path'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const generateUrl = vi.hoisted(() => vi.fn((url) => join('/ROOT', url)))
|
||||
|
||||
vi.mock('@nextcloud/router', () => ({
|
||||
generateUrl,
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
|
||||
describe('OC.MimeType tests', async () => {
|
||||
beforeEach(async () => {
|
||||
window.OC.MimeTypeList = {
|
||||
aliases: { 'app/foobar': 'foo/bar' },
|
||||
files: ['folder', 'folder-shared', 'folder-external', 'foo-bar', 'foo', 'file'],
|
||||
themes: {
|
||||
abc: ['folder'],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('no theme', async () => {
|
||||
beforeEach(async () => {
|
||||
window.OC.theme ??= {}
|
||||
window.OC.theme.folder = ''
|
||||
})
|
||||
|
||||
it.for([
|
||||
// returns the correct icon for a mimetype
|
||||
{ mimeType: 'file', icon: 'file' },
|
||||
{ mimeType: 'dir', icon: 'folder' },
|
||||
{ mimeType: 'dir-shared', icon: 'folder-shared' },
|
||||
{ mimeType: 'dir-external', icon: 'folder-external' },
|
||||
// returns the correct icon for a mimetype for which we have an icon
|
||||
{ mimeType: 'foo/bar', icon: 'foo-bar' },
|
||||
// returns the correct icon for a mimetype for which we only have a general mimetype icon
|
||||
{ mimeType: 'foo/baz', icon: 'foo' },
|
||||
// return the file mimetype if we have no matching icon but do have a file icon
|
||||
{ mimeType: 'foobar', icon: 'file' },
|
||||
])('returns correct icon', async ({ icon, mimeType }) => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl(mimeType)).toEqual(`/ROOT/core/img/filetypes/${icon}.svg`)
|
||||
})
|
||||
|
||||
it('returns undefined if the an icon for undefined is requested', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl(undefined)).toEqual(undefined)
|
||||
})
|
||||
|
||||
it('uses the cache if available', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(generateUrl).not.toHaveBeenCalled()
|
||||
|
||||
expect(getIconUrl('dir')).toEqual('/ROOT/core/img/filetypes/folder.svg')
|
||||
expect(generateUrl).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(getIconUrl('dir')).toEqual('/ROOT/core/img/filetypes/folder.svg')
|
||||
expect(generateUrl).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(getIconUrl('dir-shared')).toEqual('/ROOT/core/img/filetypes/folder-shared.svg')
|
||||
expect(generateUrl).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('converts aliases correctly', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl('app/foobar')).toEqual('/ROOT/core/img/filetypes/foo-bar.svg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with legacy themes', async () => {
|
||||
beforeEach(async () => {
|
||||
window.OC.theme ??= {}
|
||||
window.OC.theme.folder = 'abc'
|
||||
})
|
||||
|
||||
it('uses theme path if a theme icon is availble', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl('dir')).toEqual('/ROOT/themes/abc/core/img/filetypes/folder.svg')
|
||||
})
|
||||
|
||||
it('fallbacks to the default theme if no icon is available in the theme', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl('dir-shared')).toEqual('/ROOT/core/img/filetypes/folder-shared.svg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with theming app', async () => {
|
||||
beforeEach(async () => {
|
||||
window.OC.theme ??= {}
|
||||
window.OC.theme.folder = ''
|
||||
window.OCA.Theming ??= {}
|
||||
window.OCA.Theming.cacheBuster = '1cacheBuster2'
|
||||
})
|
||||
|
||||
it('uses the correct theming URL', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl('dir')).toMatch('/apps/theming/img/core/filetypes/folder.svg')
|
||||
})
|
||||
|
||||
it('uses the cache buster', async () => {
|
||||
const { getIconUrl } = await getMethod()
|
||||
expect(getIconUrl('file')).toMatch(/\?v=1cacheBuster2$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function getMethod() {
|
||||
return await import('../../OC/mimeType.js')
|
||||
}
|
||||
Loading…
Reference in a new issue