diff --git a/addons/docs/src/frameworks/vue/sourceDecorator.test.ts b/addons/docs/src/frameworks/vue/sourceDecorator.test.ts index b02569d9aa62..54695c1ea84f 100644 --- a/addons/docs/src/frameworks/vue/sourceDecorator.test.ts +++ b/addons/docs/src/frameworks/vue/sourceDecorator.test.ts @@ -30,6 +30,66 @@ describe('vnodeToString', () => { ).toMatchInlineSnapshot(``); }); + it('static class', () => { + expect( + vnodeToString( + getVNode({ + template: ``, + }) + ) + ).toMatchInlineSnapshot(``); + }); + + it('string dynamic class', () => { + expect( + vnodeToString( + getVNode({ + template: ``, + }) + ) + ).toMatchInlineSnapshot(``); + }); + + it('non-string dynamic class', () => { + expect( + vnodeToString( + getVNode({ + template: ``, + }) + ) + ).toMatchInlineSnapshot(``); + }); + + it('array dynamic class', () => { + expect( + vnodeToString( + getVNode({ + template: ``, + }) + ) + ).toMatchInlineSnapshot(``); + }); + + it('object dynamic class', () => { + expect( + vnodeToString( + getVNode({ + template: ``, + }) + ) + ).toMatchInlineSnapshot(``); + }); + + it('merge dynamic and static classes', () => { + expect( + vnodeToString( + getVNode({ + template: ``, + }) + ) + ).toMatchInlineSnapshot(``); + }); + it('attributes', () => { const MyComponent: ComponentOptions = { props: ['propA', 'propB', 'propC', 'propD', 'propE', 'propF', 'propG'], diff --git a/addons/docs/src/frameworks/vue/sourceDecorator.ts b/addons/docs/src/frameworks/vue/sourceDecorator.ts index 04975f10420c..40831a68f70c 100644 --- a/addons/docs/src/frameworks/vue/sourceDecorator.ts +++ b/addons/docs/src/frameworks/vue/sourceDecorator.ts @@ -76,6 +76,7 @@ export const sourceDecorator = (storyFn: any, context: StoryContext) => { export function vnodeToString(vnode: Vue.VNode): string { const attrString = [ ...(vnode.data?.slot ? ([['slot', vnode.data.slot]] as [string, any][]) : []), + ['class', stringifyClassAttribute(vnode)], ...(vnode.componentOptions?.propsData ? Object.entries(vnode.componentOptions.propsData) : []), ...(vnode.data?.attrs ? Object.entries(vnode.data.attrs) : []), ] @@ -122,6 +123,43 @@ export function vnodeToString(vnode: Vue.VNode): string { .join('')}`; } +function stringifyClassAttribute(vnode: Vue.VNode): string | undefined { + if (!vnode.data || (!vnode.data.staticClass && !vnode.data.class)) { + return undefined; + } + + return ( + [...(vnode.data.staticClass?.split(' ') ?? []), ...normalizeClassBinding(vnode.data.class)] + .filter(Boolean) + .join(' ') || undefined + ); +} + +// https://vuejs.org/v2/guide/class-and-style.html#Binding-HTML-Classes +function normalizeClassBinding(binding: unknown): readonly string[] { + if (!binding) { + return []; + } + + if (typeof binding === 'string') { + return [binding]; + } + + if (binding instanceof Array) { + // To handle an object-in-array binding smartly, we use recursion + return binding.map(normalizeClassBinding).reduce((a, b) => [...a, ...b], []); + } + + if (typeof binding === 'object') { + return Object.entries(binding) + .filter(([, active]) => !!active) + .map(([className]) => className); + } + + // Unknown class binding + return []; +} + function stringifyAttr(attrName: string, value?: any): string | null { if (typeof value === 'undefined' || typeof value === 'function') { return null;