4 changed files with 185 additions and 9 deletions
@ -0,0 +1,130 @@ |
|||||
|
/* eslint-disable vue/one-component-per-file, vue/require-default-prop */ |
||||
|
|
||||
|
import type { App, Component } from 'vue'; |
||||
|
|
||||
|
import { createApp, defineComponent, h } from 'vue'; |
||||
|
|
||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
||||
|
|
||||
|
import { createThemeAwareButton } from '../theme-aware-button'; |
||||
|
|
||||
|
const mocks = vi.hoisted(() => ({ |
||||
|
compact: false, |
||||
|
contextTheme: undefined as object | undefined, |
||||
|
isDark: false, |
||||
|
providerThemes: [] as any[], |
||||
|
tokens: { colorPrimary: '#e11d48' }, |
||||
|
})); |
||||
|
|
||||
|
vi.mock('@vben/hooks', () => ({ |
||||
|
useAntdDesignTokens: () => ({ tokens: mocks.tokens }), |
||||
|
})); |
||||
|
|
||||
|
vi.mock('@vben/preferences', () => ({ |
||||
|
preferences: { |
||||
|
app: { |
||||
|
get compact() { |
||||
|
return mocks.compact; |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
usePreferences: () => ({ |
||||
|
isDark: { |
||||
|
get value() { |
||||
|
return mocks.isDark; |
||||
|
}, |
||||
|
}, |
||||
|
}), |
||||
|
})); |
||||
|
|
||||
|
vi.mock('antdv-next/config-provider/context', () => ({ |
||||
|
useConfig: () => ({ value: { theme: mocks.contextTheme } }), |
||||
|
})); |
||||
|
|
||||
|
vi.mock('antdv-next', async () => { |
||||
|
const { defineComponent, h } = |
||||
|
await vi.importActual<typeof import('vue')>('vue'); |
||||
|
return { |
||||
|
ConfigProvider: defineComponent({ |
||||
|
props: { theme: Object }, |
||||
|
setup(props, { slots }) { |
||||
|
mocks.providerThemes.push(props.theme); |
||||
|
return () => |
||||
|
h('section', { 'data-theme-provider': '' }, slots.default?.()); |
||||
|
}, |
||||
|
}), |
||||
|
theme: { |
||||
|
compactAlgorithm: 'compact', |
||||
|
darkAlgorithm: 'dark', |
||||
|
defaultAlgorithm: 'default', |
||||
|
}, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const Button = defineComponent({ |
||||
|
inheritAttrs: false, |
||||
|
props: { type: String }, |
||||
|
setup(props, { attrs, slots }) { |
||||
|
return () => |
||||
|
h( |
||||
|
'button', |
||||
|
{ ...attrs, 'data-button-type': props.type }, |
||||
|
slots.default?.(), |
||||
|
); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
let activeApp: App | undefined; |
||||
|
|
||||
|
function mountButton(component: Component) { |
||||
|
const container = document.createElement('div'); |
||||
|
document.body.append(container); |
||||
|
activeApp = createApp(() => |
||||
|
h(component, { 'data-probe': 'button' }, () => 'Submit'), |
||||
|
); |
||||
|
activeApp.mount(container); |
||||
|
return container; |
||||
|
} |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
mocks.compact = false; |
||||
|
mocks.contextTheme = undefined; |
||||
|
mocks.isDark = false; |
||||
|
mocks.providerThemes.length = 0; |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
activeApp?.unmount(); |
||||
|
activeApp = undefined; |
||||
|
document.body.innerHTML = ''; |
||||
|
}); |
||||
|
|
||||
|
describe('createThemeAwareButton', () => { |
||||
|
it('uses the existing ConfigProvider context without adding a wrapper', () => { |
||||
|
mocks.contextTheme = { token: mocks.tokens }; |
||||
|
const container = mountButton(createThemeAwareButton(Button, 'primary')); |
||||
|
|
||||
|
expect(container.querySelector('[data-theme-provider]')).toBeNull(); |
||||
|
expect(container.querySelector('button')?.dataset.buttonType).toBe( |
||||
|
'primary', |
||||
|
); |
||||
|
expect(container.querySelector('button')?.dataset.probe).toBe('button'); |
||||
|
}); |
||||
|
|
||||
|
it('provides the current Vben theme when no ConfigProvider is present', () => { |
||||
|
mocks.compact = true; |
||||
|
mocks.isDark = true; |
||||
|
const container = mountButton(createThemeAwareButton(Button, 'default')); |
||||
|
|
||||
|
expect(container.querySelector('[data-theme-provider]')).not.toBeNull(); |
||||
|
expect(mocks.providerThemes).toEqual([ |
||||
|
{ |
||||
|
algorithm: ['dark', 'compact'], |
||||
|
token: mocks.tokens, |
||||
|
}, |
||||
|
]); |
||||
|
expect(container.querySelector('button')?.dataset.buttonType).toBe( |
||||
|
'default', |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,47 @@ |
|||||
|
import type { Component } from 'vue'; |
||||
|
|
||||
|
import { computed, defineComponent, h } from 'vue'; |
||||
|
|
||||
|
import { useAntdDesignTokens } from '@vben/hooks'; |
||||
|
import { preferences, usePreferences } from '@vben/preferences'; |
||||
|
|
||||
|
import { ConfigProvider, theme } from 'antdv-next'; |
||||
|
import { useConfig } from 'antdv-next/config-provider/context'; |
||||
|
|
||||
|
function createThemeAwareButton( |
||||
|
Button: Component, |
||||
|
type: 'default' | 'primary', |
||||
|
) { |
||||
|
return defineComponent({ |
||||
|
inheritAttrs: false, |
||||
|
setup(props, { attrs, slots }) { |
||||
|
const config = useConfig(); |
||||
|
if (config.value?.theme) { |
||||
|
return () => h(Button, { ...attrs, ...props, type }, slots); |
||||
|
} |
||||
|
|
||||
|
const { isDark } = usePreferences(); |
||||
|
const { tokens } = useAntdDesignTokens(); |
||||
|
const buttonTheme = computed(() => { |
||||
|
const algorithm = [ |
||||
|
isDark.value ? theme.darkAlgorithm : theme.defaultAlgorithm, |
||||
|
]; |
||||
|
if (preferences.app.compact) { |
||||
|
algorithm.push(theme.compactAlgorithm); |
||||
|
} |
||||
|
return { algorithm, token: tokens }; |
||||
|
}); |
||||
|
|
||||
|
return () => |
||||
|
h( |
||||
|
ConfigProvider, |
||||
|
{ theme: buttonTheme.value }, |
||||
|
{ |
||||
|
default: () => h(Button, { ...attrs, ...props, type }, slots), |
||||
|
}, |
||||
|
); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export { createThemeAwareButton }; |
||||
Loading…
Reference in new issue