mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
refactor(core): migrate OC.msg away from jQuery
Make the class jQuery free to be able to drop it as a dependency. Also added some unit tests for it. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
6a67456574
commit
f175e421b3
3 changed files with 335 additions and 99 deletions
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
|
||||
/**
|
||||
* A little class to manage a status field for a "saving" process.
|
||||
* It can be used to display a starting message (e.g. "Saving...") and then
|
||||
* replace it with a green success message or a red error message.
|
||||
*
|
||||
* @namespace OC.msg
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Displayes a "Saving..." message in the given message placeholder
|
||||
*
|
||||
* @param {object} selector Placeholder to display the message in
|
||||
*/
|
||||
startSaving(selector) {
|
||||
this.startAction(selector, t('core', 'Saving …'))
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes a custom message in the given message placeholder
|
||||
*
|
||||
* @param {object} selector Placeholder to display the message in
|
||||
* @param {string} message Plain text message to display (no HTML allowed)
|
||||
*/
|
||||
startAction(selector, message) {
|
||||
$(selector).text(message)
|
||||
.removeClass('success')
|
||||
.removeClass('error')
|
||||
.stop(true, true)
|
||||
.show()
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success/error message in the given selector
|
||||
*
|
||||
* @param {object} selector Placeholder to display the message in
|
||||
* @param {object} response Response of the server
|
||||
* @param {object} response.data Data of the servers response
|
||||
* @param {string} response.data.message Plain text message to display (no HTML allowed)
|
||||
* @param {string} response.status is being used to decide whether the message
|
||||
* is displayed as an error/success
|
||||
*/
|
||||
finishedSaving(selector, response) {
|
||||
this.finishedAction(selector, response)
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success/error message in the given selector
|
||||
*
|
||||
* @param {object} selector Placeholder to display the message in
|
||||
* @param {object} response Response of the server
|
||||
* @param {object} response.data Data of the servers response
|
||||
* @param {string} response.data.message Plain text message to display (no HTML allowed)
|
||||
* @param {string} response.status is being used to decide whether the message
|
||||
* is displayed as an error/success
|
||||
*/
|
||||
finishedAction(selector, response) {
|
||||
if (response.status === 'success') {
|
||||
this.finishedSuccess(selector, response.data.message)
|
||||
} else {
|
||||
this.finishedError(selector, response.data.message)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success message in the given selector
|
||||
*
|
||||
* @param {object} selector Placeholder to display the message in
|
||||
* @param {string} message Plain text success message to display (no HTML allowed)
|
||||
*/
|
||||
finishedSuccess(selector, message) {
|
||||
$(selector).text(message)
|
||||
.addClass('success')
|
||||
.removeClass('error')
|
||||
.stop(true, true)
|
||||
.delay(3000)
|
||||
.fadeOut(900)
|
||||
.show()
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an error message in the given selector
|
||||
*
|
||||
* @param {object} selector Placeholder to display the message in
|
||||
* @param {string} message Plain text error message to display (no HTML allowed)
|
||||
*/
|
||||
finishedError(selector, message) {
|
||||
$(selector).text(message)
|
||||
.addClass('error')
|
||||
.removeClass('success')
|
||||
.show()
|
||||
},
|
||||
}
|
||||
195
core/src/OC/msg.spec.ts
Normal file
195
core/src/OC/msg.spec.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { afterAll, describe, expect, it, test, vi } from 'vitest'
|
||||
import msg from './msg.ts'
|
||||
|
||||
describe('start action', () => {
|
||||
it('sets the message text content', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
msg.startAction(selector, 'the message')
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
expect(el).not.toBeNull()
|
||||
expect(el!.textContent).toBe('the message')
|
||||
})
|
||||
|
||||
it('removes old classes', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder" class="success"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
expect(el).not.toBeNull()
|
||||
expect(el!.classList.contains('success')).toBe(true)
|
||||
|
||||
msg.startAction(selector, 'the message')
|
||||
expect(el!.classList.contains('success')).toBe(false)
|
||||
})
|
||||
|
||||
it('sets element visible', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder" style="display: none;"></div>'
|
||||
|
||||
const el = document.querySelector(selector) as HTMLElement
|
||||
expect(el).not.toBeNull()
|
||||
expect(el.style.display).toBe('none')
|
||||
|
||||
msg.startAction(selector, 'the message')
|
||||
expect(el.style.display).toBe('block')
|
||||
})
|
||||
})
|
||||
|
||||
test('start saving message', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
msg.startSaving(selector)
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
expect(el).not.toBeNull()
|
||||
expect(el!.textContent).toBe('Saving …')
|
||||
})
|
||||
|
||||
describe('finish with error', () => {
|
||||
it('sets the message text content', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
msg.startSaving(selector)
|
||||
msg.finishedError(selector, 'error message')
|
||||
expect(el!.textContent).toBe('error message')
|
||||
})
|
||||
|
||||
it('adds error class', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
msg.startSaving(selector)
|
||||
msg.finishedError(selector, 'error message')
|
||||
expect(el!.classList.contains('error')).toBe(true)
|
||||
})
|
||||
|
||||
it('removes old classes', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder" class="success"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
msg.startSaving(selector)
|
||||
msg.finishedError(selector, 'error message')
|
||||
expect(el!.classList.contains('success')).toBe(false)
|
||||
})
|
||||
|
||||
it('sets element visible', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder" style="display: none;"></div>'
|
||||
|
||||
const el = document.querySelector(selector) as HTMLElement
|
||||
msg.startSaving(selector)
|
||||
msg.finishedError(selector, 'error message')
|
||||
expect(el.style.display).toBe('block')
|
||||
})
|
||||
})
|
||||
|
||||
describe('finish with success', () => {
|
||||
afterAll(() => vi.useRealTimers())
|
||||
|
||||
it('sets the message text content', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
msg.startSaving(selector)
|
||||
msg.finishedSuccess(selector, 'success message')
|
||||
expect(el!.textContent).toBe('success message')
|
||||
})
|
||||
|
||||
it('adds success class', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
msg.startSaving(selector)
|
||||
msg.finishedSuccess(selector, 'success message')
|
||||
expect(el!.classList.contains('success')).toBe(true)
|
||||
})
|
||||
|
||||
it('removes old classes', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder" class="error"></div>'
|
||||
|
||||
const el = document.querySelector(selector)
|
||||
msg.startSaving(selector)
|
||||
msg.finishedSuccess(selector, 'success message')
|
||||
expect(el!.classList.contains('error')).toBe(false)
|
||||
})
|
||||
|
||||
it('sets element visible', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder" style="display: none;"></div>'
|
||||
|
||||
const el = document.querySelector(selector) as HTMLElement
|
||||
msg.startSaving(selector)
|
||||
msg.finishedSuccess(selector, 'success message')
|
||||
expect(el.style.display).toBe('block')
|
||||
})
|
||||
|
||||
it('fades out element', () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const el = document.querySelector(selector) as HTMLElement
|
||||
msg.startSaving(selector)
|
||||
msg.finishedSuccess(selector, 'success message')
|
||||
expect(el!.style.display).toBe('block')
|
||||
|
||||
vi.advanceTimersByTime(3900)
|
||||
|
||||
expect(el.style.display).toBe('none')
|
||||
})
|
||||
})
|
||||
|
||||
describe('finished action', () => {
|
||||
it('calls finishedSuccess on success response', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const finishedSuccessSpy = vi.spyOn(msg, 'finishedSuccess')
|
||||
const response = { data: { message: 'all good' }, status: 'success' }
|
||||
|
||||
msg.finishedAction(selector, response)
|
||||
|
||||
expect(finishedSuccessSpy).toHaveBeenCalledWith(selector, 'all good')
|
||||
})
|
||||
|
||||
it('calls finishedError on error response', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const finishedErrorSpy = vi.spyOn(msg, 'finishedError')
|
||||
const response = { data: { message: 'something went wrong' }, status: 'error' }
|
||||
|
||||
msg.finishedAction(selector, response)
|
||||
|
||||
expect(finishedErrorSpy).toHaveBeenCalledWith(selector, 'something went wrong')
|
||||
})
|
||||
})
|
||||
|
||||
test('finished saving delegates to finished action', () => {
|
||||
const selector = '#msg-placeholder'
|
||||
document.body.innerHTML = '<div id="msg-placeholder"></div>'
|
||||
|
||||
const finishedActionSpy = vi.spyOn(msg, 'finishedAction')
|
||||
const response = { data: { message: 'done saving' }, status: 'success' }
|
||||
|
||||
msg.finishedSaving(selector, response)
|
||||
|
||||
expect(finishedActionSpy).toHaveBeenCalledWith(selector, response)
|
||||
})
|
||||
140
core/src/OC/msg.ts
Normal file
140
core/src/OC/msg.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
/**
|
||||
* A little class to manage a status field for a "saving" process.
|
||||
* It can be used to display a starting message (e.g. "Saving...") and then
|
||||
* replace it with a green success message or a red error message.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Displayes a "Saving..." message in the given message placeholder
|
||||
*
|
||||
* @param selector - Query selectior for the element to display the message in
|
||||
*/
|
||||
startSaving(selector: string) {
|
||||
this.startAction(selector, t('core', 'Saving …'))
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes a custom message in the given message placeholder
|
||||
*
|
||||
* @param selector - Query selectior for the element to display the message in
|
||||
* @param message - Plain text message to display (no HTML allowed)
|
||||
*/
|
||||
startAction(selector: string, message: string) {
|
||||
const el = document.querySelector(selector)
|
||||
if (!el || !(el instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
el.textContent = message
|
||||
el.classList.remove('success')
|
||||
el.classList.remove('error')
|
||||
el.getAnimations?.().forEach((animation) => animation.cancel())
|
||||
el.style.display = 'block'
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success/error message in the given selector
|
||||
*
|
||||
* @param selector - Query selectior for the element to display the message in
|
||||
* @param response - Response of the server
|
||||
* @param response.data - Data of the servers response
|
||||
* @param response.data.message - Plain text message to display (no HTML allowed)
|
||||
* @param response.status - is being used to decide whether the message is displayed as an error/success
|
||||
*/
|
||||
finishedSaving(selector: string, response: { data: { message: string }, status: string }) {
|
||||
this.finishedAction(selector, response)
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success/error message in the given selector
|
||||
*
|
||||
* @param selector - Query selector for the element to display the message in
|
||||
* @param response - Response of the server
|
||||
* @param response.data - Data of the servers response
|
||||
* @param response.data.message - Plain text message to display (no HTML allowed)
|
||||
* @param response.status . Is being used to decide whether the message is displayed as an error/success
|
||||
*/
|
||||
finishedAction(selector: string, response: { data: { message: string }, status: string }) {
|
||||
if (response.status === 'success') {
|
||||
this.finishedSuccess(selector, response.data.message)
|
||||
} else {
|
||||
this.finishedError(selector, response.data.message)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success message in the given selector
|
||||
*
|
||||
* @param selector - Query selector for the element to display the message in
|
||||
* @param message - Plain text success message to display (no HTML allowed)
|
||||
*/
|
||||
finishedSuccess(selector: string, message: string) {
|
||||
const el = document.querySelector(selector)
|
||||
if (!el || !(el instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
el.textContent = message
|
||||
el.classList.remove('error')
|
||||
el.classList.add('success')
|
||||
el.getAnimations?.().forEach((animation) => animation.cancel())
|
||||
|
||||
window.setTimeout(fadeOut, 3000)
|
||||
el.style.display = 'block'
|
||||
|
||||
/**
|
||||
* Fades out the message element
|
||||
*/
|
||||
function fadeOut() {
|
||||
if (!el || !(el instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
const animation = el.animate?.(
|
||||
[
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 },
|
||||
],
|
||||
{
|
||||
duration: 900,
|
||||
fill: 'forwards',
|
||||
},
|
||||
)
|
||||
|
||||
if (animation) {
|
||||
animation.addEventListener('finish', () => {
|
||||
el.style.display = 'none'
|
||||
})
|
||||
} else {
|
||||
window.setTimeout(() => {
|
||||
el.style.display = 'none'
|
||||
}, 900)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an error message in the given selector
|
||||
*
|
||||
* @param selector - Query selector for the element to display the message in
|
||||
* @param message - Plain text error message to display (no HTML allowed)
|
||||
*/
|
||||
finishedError(selector: string, message: string) {
|
||||
const el = document.querySelector(selector)
|
||||
if (!el || !(el instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
el.textContent = message
|
||||
el.classList.remove('success')
|
||||
el.classList.add('error')
|
||||
el.style.display = 'block'
|
||||
},
|
||||
}
|
||||
Loading…
Reference in a new issue