From 117b8a51137795d8e8f3a2311b85ee989c0a0bf1 Mon Sep 17 00:00:00 2001 From: addshore Date: Wed, 30 Sep 2026 23:55:22 +0100 Subject: [PATCH] Allow SVG selection in the wiki logo uploader Accept SVG alongside PNG and recommend square SVG logos. Document the 2 MiB limit and PNG size guidance. Display API validation errors without reloading the page or discarding the selected file. Add uploader regression tests. API change needed https://github.com/wbstack/api/pull/1286 before this UI change makes any sense. Bug: T356389 --- .../Pages/ManageWiki/Cards/Logo.vue | 10 +-- tests/unit/logo.spec.js | 68 +++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 tests/unit/logo.spec.js diff --git a/src/components/Pages/ManageWiki/Cards/Logo.vue b/src/components/Pages/ManageWiki/Cards/Logo.vue index 839ea248..3f287278 100644 --- a/src/components/Pages/ManageWiki/Cards/Logo.vue +++ b/src/components/Pages/ManageWiki/Cards/Logo.vue @@ -3,10 +3,10 @@ Set Logo { - console.log(err.response) - alert('Something went wrong.') - this.$router.go() + console.error(err) + const errors = err.response && err.response.data && err.response.data.errors + alert(errors && errors.logo ? errors.logo.join('\n') : 'The logo could not be uploaded. Please try again.') }) }, }, diff --git a/tests/unit/logo.spec.js b/tests/unit/logo.spec.js new file mode 100644 index 00000000..83e8fa36 --- /dev/null +++ b/tests/unit/logo.spec.js @@ -0,0 +1,68 @@ +import { shallowMount } from '@vue/test-utils' +import Vue from 'vue' +import Vuetify from 'vuetify' +import Logo from '@/components/Pages/ManageWiki/Cards/Logo.vue' + +Vue.use(Vuetify) + +describe('Logo uploads', () => { + let wrapper + let dispatch + + beforeEach(() => { + dispatch = jest.fn().mockResolvedValue() + jest.spyOn(window, 'alert').mockImplementation(() => {}) + jest.spyOn(console, 'error').mockImplementation(() => {}) + wrapper = shallowMount(Logo, { + propsData: { wikiId: 1 }, + mocks: { + $store: { + dispatch, + state: { wikis: { currentWikiSettings: { wgLogo: 'logo.png' } } }, + }, + }, + }) + }) + + afterEach(() => { + wrapper.destroy() + jest.restoreAllMocks() + }) + + it('accepts SVG and PNG and explains the recommended format and limits', () => { + const input = wrapper.findComponent({ name: 'v-file-input' }) + expect(input.attributes('accept')).toBe('image/png,image/svg+xml,.png,.svg') + expect(input.attributes('hint')).toContain('SVG (recommended)') + expect(input.attributes('hint')).toContain('135x135') + expect(input.attributes('hint')).toContain('2 MiB') + }) + + it('uploads the selected SVG without changing its filename or contents', async () => { + const file = new File([''], 'logo.svg', { type: 'image/svg+xml' }) + wrapper.vm.onLogoFileChanged(file) + wrapper.vm.doLogoUpload() + await Promise.resolve() + expect(dispatch).toHaveBeenCalledWith('updateLogo', { wikiId: 1, file, fileName: 'logo.svg' }) + expect(window.alert).toHaveBeenCalledWith('Upload success!') + }) + + it('shows API validation errors without discarding the selected file', async () => { + const file = new File(['invalid'], 'logo.svg', { type: 'image/svg+xml' }) + dispatch.mockRejectedValue({ response: { data: { errors: { logo: ['Invalid SVG logo.'] } } } }) + wrapper.vm.onLogoFileChanged(file) + wrapper.vm.doLogoUpload() + await Promise.resolve() + await Promise.resolve() + expect(window.alert).toHaveBeenCalledWith('Invalid SVG logo.') + expect(wrapper.vm.selectedLogoFile).toBe(file) + }) + + it('shows an error when the network request fails', async () => { + dispatch.mockRejectedValue(new Error('Network error')) + wrapper.vm.onLogoFileChanged(new File(['data'], 'logo.png')) + wrapper.vm.doLogoUpload() + await Promise.resolve() + await Promise.resolve() + expect(window.alert).toHaveBeenCalledWith('The logo could not be uploaded. Please try again.') + }) +})