fix(OC\MimeType): use proper webroot if needed

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2025-10-22 20:25:59 +02:00 committed by nextcloud-command
parent 52842415fb
commit 8519b71bf1
2 changed files with 60 additions and 93 deletions

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { generateUrl } from '@nextcloud/router'
import { generateUrl, getRootUrl } from '@nextcloud/router'
const iconCache = new Map()
@ -23,36 +23,24 @@ export function getIconUrl(mimeType) {
}
if (!iconCache.has(mimeType)) {
// First try to get the correct icon from the current theme
let gotIcon = null
let gotIcon = false
let path = ''
// First try to get the correct icon from the current legacy-theme
if (OC.theme.folder !== '' && Array.isArray(OC.MimeTypeList.themes[OC.theme.folder])) {
path = generateUrl('/themes/' + window.OC.theme.folder + '/core/img/filetypes/')
path = getRootUrl() + '/themes/' + window.OC.theme.folder + '/core/img/filetypes/'
const icon = getMimeTypeIcon(mimeType, window.OC.MimeTypeList.themes[OC.theme.folder])
if (icon !== null) {
if (icon) {
gotIcon = true
path += icon
path += icon + '.svg'
}
}
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
// theming is always enabled since Nextcloud 20 so we get it from that
if (!gotIcon) {
path = generateUrl('/apps/theming/img/core/filetypes/' + getMimeTypeIcon(mimeType, window.OC.MimeTypeList.files) + '.svg')
}
path += '?v=' + window.OCA.Theming.cacheBuster
// Cache the result
iconCache.set(mimeType, path)
}
@ -92,3 +80,10 @@ function getMimeTypeIcon(mimeType, files) {
return null
}
/**
* Clear the icon cache
*/
export function clearIconCache() {
iconCache.clear()
}

View file

@ -3,22 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { join } from 'node:path'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { beforeEach, describe, expect, it, test } from 'vitest'
import { clearIconCache, getIconUrl } from '../../OC/mimeType.js'
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 () => {
describe('OC.MimeType tests', () => {
beforeEach(() => {
window.OC.MimeTypeList = {
aliases: { 'app/foobar': 'foo/bar' },
files: ['folder', 'folder-shared', 'folder-external', 'foo-bar', 'foo', 'file'],
@ -26,11 +15,42 @@ describe('OC.MimeType tests', async () => {
abc: ['folder'],
},
}
// @ts-expect-error - mocking global variable
window._oc_webroot = '/ROOT'
// setup for legacy theme
window.OC.theme ??= {}
window.OC.theme.folder = ''
// the theming app is always enabled since Nextcloud 20
window.OCA.Theming ??= {}
window.OCA.Theming.cacheBuster = '1cacheBuster2'
clearIconCache()
})
describe('no theme', async () => {
beforeEach(async () => {
window.OC.theme ??= {}
test('uses icon cache if availble', async () => {
window.OC.theme.folder = 'abc'
expect(getIconUrl('dir')).toEqual('/ROOT/themes/abc/core/img/filetypes/folder.svg?v=1cacheBuster2')
window.OC.theme.folder = ''
expect(getIconUrl('dir')).toEqual('/ROOT/themes/abc/core/img/filetypes/folder.svg?v=1cacheBuster2')
clearIconCache()
expect(getIconUrl('dir')).toEqual('/ROOT/index.php/apps/theming/img/core/filetypes/folder.svg?v=1cacheBuster2')
})
describe('with legacy themes', async () => {
beforeEach(() => {
window.OC.theme.folder = 'abc'
})
it('uses theme path if a theme icon is availble', async () => {
expect(getIconUrl('dir')).toEqual('/ROOT/themes/abc/core/img/filetypes/folder.svg?v=1cacheBuster2')
})
it('fallbacks to the default theme if no icon is available in the theme', async () => {
expect(getIconUrl('dir-shared')).toEqual('/ROOT/index.php/apps/theming/img/core/filetypes/folder-shared.svg?v=1cacheBuster2')
})
})
describe('no legacy theme', async () => {
beforeEach(() => {
window.OC.theme.folder = ''
})
@ -47,72 +67,24 @@ describe('OC.MimeType tests', async () => {
// 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`)
expect(getIconUrl(mimeType)).toEqual(`/ROOT/index.php/apps/theming/img/core/filetypes/${icon}.svg?v=1cacheBuster2`)
})
it('returns undefined if the an icon for undefined is requested', async () => {
const { getIconUrl } = await getMethod()
// @ts-expect-error - testing invalid input
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'
expect(getIconUrl('app/foobar')).toEqual('/ROOT/index.php/apps/theming/img/core/filetypes/foo-bar.svg?v=1cacheBuster2')
})
it('uses the correct theming URL', async () => {
const { getIconUrl } = await getMethod()
expect(getIconUrl('dir')).toMatch('/apps/theming/img/core/filetypes/folder.svg')
expect(getIconUrl('dir')).toMatch('/ROOT/index.php/apps/theming/img/core/filetypes/folder.svg?v=1cacheBuster2')
})
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')
}