diff --git a/packages/buefy-next/rollup.config.mjs b/packages/buefy-next/rollup.config.mjs index aaf0bc682..ea4bf26f3 100644 --- a/packages/buefy-next/rollup.config.mjs +++ b/packages/buefy-next/rollup.config.mjs @@ -55,7 +55,6 @@ const JS_COMPONENTS = [ 'tag', 'taginput', 'timepicker', - 'toast', 'upload', ] diff --git a/packages/buefy-next/src/components/toast/Toast.spec.js b/packages/buefy-next/src/components/toast/Toast.spec.ts similarity index 77% rename from packages/buefy-next/src/components/toast/Toast.spec.js rename to packages/buefy-next/src/components/toast/Toast.spec.ts index 1e99a18f7..ee500071e 100644 --- a/packages/buefy-next/src/components/toast/Toast.spec.js +++ b/packages/buefy-next/src/components/toast/Toast.spec.ts @@ -1,11 +1,13 @@ import { transformVNodeArgs } from 'vue' import { shallowMount } from '@vue/test-utils' +import type { VueWrapper } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { BToast, ToastProgrammatic } from '@components/toast' -let wrapper +let wrapper: VueWrapper> describe('BToast', () => { - HTMLElement.prototype.insertAdjacentElement = jest.fn() + HTMLElement.prototype.insertAdjacentElement = vi.fn() beforeEach(() => { wrapper = shallowMount( BToast, @@ -38,32 +40,32 @@ describe('BToast', () => { }) afterEach(() => { - jest.useRealTimers() + vi.useRealTimers() }) it('should close after the duration', () => { - jest.useFakeTimers() + vi.useFakeTimers() const params = { message: 'message', duration: 1000, - onClose: jest.fn() + onClose: vi.fn() } new ToastProgrammatic().open(params) - jest.advanceTimersByTime(500) + vi.advanceTimersByTime(500) expect(params.onClose).not.toHaveBeenCalled() - jest.advanceTimersByTime(500) + vi.advanceTimersByTime(500) expect(params.onClose).toHaveBeenCalled() }) it('indefinitely should be able to be manually closed', () => { - jest.useFakeTimers() + vi.useFakeTimers() const params = { message: 'message', indefinite: true, - onClose: jest.fn() + onClose: vi.fn() } const toast = new ToastProgrammatic().open(params) - jest.advanceTimersByTime(10000) + vi.advanceTimersByTime(10000) expect(params.onClose).not.toHaveBeenCalled() toast.close() expect(params.onClose).toHaveBeenCalled() diff --git a/packages/buefy-next/src/components/toast/Toast.vue b/packages/buefy-next/src/components/toast/Toast.vue index 6955c895c..774086f28 100644 --- a/packages/buefy-next/src/components/toast/Toast.vue +++ b/packages/buefy-next/src/components/toast/Toast.vue @@ -23,11 +23,14 @@ - diff --git a/packages/buefy-next/src/components/toast/__snapshots__/Toast.spec.js.snap b/packages/buefy-next/src/components/toast/__snapshots__/Toast.spec.js.snap deleted file mode 100644 index 8abc5b8c2..000000000 --- a/packages/buefy-next/src/components/toast/__snapshots__/Toast.spec.js.snap +++ /dev/null @@ -1,10 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BToast render correctly 1`] = ` - - - -`; diff --git a/packages/buefy-next/src/components/toast/__snapshots__/Toast.spec.ts.snap b/packages/buefy-next/src/components/toast/__snapshots__/Toast.spec.ts.snap new file mode 100644 index 000000000..0f701e7db --- /dev/null +++ b/packages/buefy-next/src/components/toast/__snapshots__/Toast.spec.ts.snap @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BToast > render correctly 1`] = ` +" +
+ +
+
+
" +`; diff --git a/packages/buefy-next/src/components/toast/index.js b/packages/buefy-next/src/components/toast/index.ts similarity index 60% rename from packages/buefy-next/src/components/toast/index.js rename to packages/buefy-next/src/components/toast/index.ts index bf69a91c7..280b5c1f7 100644 --- a/packages/buefy-next/src/components/toast/index.js +++ b/packages/buefy-next/src/components/toast/index.ts @@ -1,35 +1,58 @@ import { createApp, h as createElement } from 'vue' +import type { App, ComponentPublicInstance, VNode } from 'vue' import Toast from './Toast.vue' +import type { ToastProps } from './Toast.vue' import config from '../../utils/config' -import { merge, copyAppContext, getComponentFromVNode } from '../../utils/helpers' +import { copyAppContext, getComponentFromVNode } from '../../utils/helpers' import { registerComponentProgrammatic } from '../../utils/plugins' +export type ToastOpenParams = Omit & { + // programmatically opened toast can have VNode(s) as the message + message?: string | VNode | (string | VNode)[], + onClose?: () => void +} + +// Minimal definition of a programmatically opened toast. +// +// ESLint does not like `{}` as a type but allowed here to make them look +// similar to Vue's definition. +/* eslint-disable @typescript-eslint/ban-types */ +type ToastProgrammaticInstance = ComponentPublicInstance< + {}, // P + {}, // B + {}, // D + {}, // C + { close: () => void } // M +> +/* eslint-enable @typescript-eslint/ban-types */ + class ToastProgrammatic { - constructor(app) { + private app: App | undefined + + constructor(app?: App) { this.app = app // may be undefined in the testing environment } - open(params) { + open(params: string | ToastOpenParams) { if (typeof params === 'string') { params = { message: params } } - const defaultParam = { - position: config.defaultToastPosition || 'is-top' - } - if (params.parent) { - delete params.parent + let slot: ToastOpenParams['message'] + let { message, ...restParams } = params + if (typeof message !== 'string') { + slot = message + message = undefined } - let slot - if (Array.isArray(params.message)) { - slot = params.message - delete params.message + const propsData: ToastProps = { + position: config.defaultToastPosition || 'is-top', + message, + ...restParams } - const propsData = merge(defaultParam, params) const container = document.createElement('div') // Vue 3 requires a new app to mount another component const vueInstance = createApp({ @@ -42,7 +65,7 @@ class ToastProgrammatic { close() { const toast = getComponentFromVNode(this.toastVNode) if (toast) { - toast.close() + (toast as InstanceType).close() } } }, @@ -76,14 +99,15 @@ class ToastProgrammatic { } else { // adds $buefy global property // so that $buefy.globalNoticeInterval is available on the new Vue app - vueInstance.config.globalProperties.$buefy = {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vueInstance.config.globalProperties.$buefy = {} as any } - return vueInstance.mount(container) + return vueInstance.mount(container) as ToastProgrammaticInstance } } const Plugin = { - install(Vue) { + install(Vue: App) { registerComponentProgrammatic(Vue, 'toast', new ToastProgrammatic(Vue)) } } diff --git a/packages/buefy-next/src/utils/vue-augmentation.ts b/packages/buefy-next/src/utils/vue-augmentation.ts index 43deb647c..dc207b9b7 100644 --- a/packages/buefy-next/src/utils/vue-augmentation.ts +++ b/packages/buefy-next/src/utils/vue-augmentation.ts @@ -10,6 +10,7 @@ import 'vue' import type { LoadingProgrammatic } from '../components/loading' import type { ModalProgrammatic } from '../components/modal' import type { SnackbarProgrammatic } from '../components/snackbar' +import type { ToastProgrammatic } from '../components/toast' import ConfigComponent from './ConfigComponent' // Augments the global property with `$buefy`. @@ -24,6 +25,7 @@ declare module '@vue/runtime-core' { loading: LoadingProgrammatic, modal: ModalProgrammatic, snackbar: SnackbarProgrammatic, + toast: ToastProgrammatic, // TODO: make key-values more specific // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any diff --git a/packages/docs/src/pages/components/toast/Toast.vue b/packages/docs/src/pages/components/toast/Toast.vue index 5d880d3be..c9e10a4bc 100644 --- a/packages/docs/src/pages/components/toast/Toast.vue +++ b/packages/docs/src/pages/components/toast/Toast.vue @@ -17,16 +17,30 @@ - diff --git a/packages/docs/src/pages/components/toast/api/toast.js b/packages/docs/src/pages/components/toast/api/toast.ts similarity index 100% rename from packages/docs/src/pages/components/toast/api/toast.js rename to packages/docs/src/pages/components/toast/api/toast.ts diff --git a/packages/docs/src/pages/components/toast/examples/ExSimple.vue b/packages/docs/src/pages/components/toast/examples/ExSimple.vue index a16309ebc..a3a03e20a 100644 --- a/packages/docs/src/pages/components/toast/examples/ExSimple.vue +++ b/packages/docs/src/pages/components/toast/examples/ExSimple.vue @@ -37,11 +37,18 @@ - diff --git a/packages/docs/src/pages/components/toast/outside-vue-instance.js b/packages/docs/src/pages/components/toast/outside-vue-instance.js new file mode 100644 index 000000000..8aeaf25cf --- /dev/null +++ b/packages/docs/src/pages/components/toast/outside-vue-instance.js @@ -0,0 +1,2 @@ +import { ToastProgrammatic as Toast } from 'buefy' +Toast.open('Toasty!') diff --git a/packages/docs/src/pages/components/toast/variables/toast.js b/packages/docs/src/pages/components/toast/variables/toast.ts similarity index 100% rename from packages/docs/src/pages/components/toast/variables/toast.js rename to packages/docs/src/pages/components/toast/variables/toast.ts