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('')}${tag}>`;
}
+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;