Skip to content

Commit

Permalink
fix(addon-docs): render Vue classes in dynamic source
Browse files Browse the repository at this point in the history
#13326

Both static class (`class="foo"`) and dynamic class (`:class="..."`)
are ignored because Vue treats them as special attribute. They don't
exist in normal vnode.data.attrs nor componentOptions.propsData.
  • Loading branch information
pocka committed Nov 29, 2020
1 parent 1cccad8 commit fe2a84b
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
60 changes: 60 additions & 0 deletions addons/docs/src/frameworks/vue/sourceDecorator.test.ts
Expand Up @@ -30,6 +30,66 @@ describe('vnodeToString', () => {
).toMatchInlineSnapshot(`<button >Button</button>`);
});

it('static class', () => {
expect(
vnodeToString(
getVNode({
template: `<button class="foo bar">Button</button>`,
})
)
).toMatchInlineSnapshot(`<button class="foo bar">Button</button>`);
});

it('string dynamic class', () => {
expect(
vnodeToString(
getVNode({
template: `<button :class="'foo'">Button</button>`,
})
)
).toMatchInlineSnapshot(`<button class="foo">Button</button>`);
});

it('non-string dynamic class', () => {
expect(
vnodeToString(
getVNode({
template: `<button :class="1">Button</button>`,
})
)
).toMatchInlineSnapshot(`<button >Button</button>`);
});

it('array dynamic class', () => {
expect(
vnodeToString(
getVNode({
template: `<button :class="['foo', null, false, 0]">Button</button>`,
})
)
).toMatchInlineSnapshot(`<button class="foo">Button</button>`);
});

it('merge dynamic and static classes', () => {
expect(
vnodeToString(
getVNode({
template: `<button class="foo" :class="{bar: null, baz: 1}">Button</button>`,
})
)
).toMatchInlineSnapshot(`<button class="foo baz">Button</button>`);
});

it('object dynamic class', () => {
expect(
vnodeToString(
getVNode({
template: `<button :class="{foo: true, bar: false}">Button</button>`,
})
)
).toMatchInlineSnapshot(`<button class="foo">Button</button>`);
});

it('attributes', () => {
const MyComponent: ComponentOptions<any, any, any> = {
props: ['propA', 'propB', 'propC', 'propD', 'propE', 'propF', 'propG'],
Expand Down
24 changes: 24 additions & 0 deletions addons/docs/src/frameworks/vue/sourceDecorator.ts
Expand Up @@ -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', normalizeClassAttribute(vnode)],
...(vnode.componentOptions?.propsData ? Object.entries(vnode.componentOptions.propsData) : []),
...(vnode.data?.attrs ? Object.entries(vnode.data.attrs) : []),
]
Expand Down Expand Up @@ -122,6 +123,29 @@ export function vnodeToString(vnode: Vue.VNode): string {
.join('')}</${tag}>`;
}

function normalizeClassAttribute(vnode: Vue.VNode): string | undefined {
if (!vnode.data || (!vnode.data.staticClass && !vnode.data.class)) {
return undefined;
}

let dynamicClass: readonly string[] = [];

if (typeof vnode.data.class === 'string') {
dynamicClass = [vnode.data.class];
} else if (vnode.data.class instanceof Array) {
dynamicClass = vnode.data.class;
} else if (typeof vnode.data.class === 'object') {
dynamicClass = Object.entries(vnode.data.class)
.filter(([, active]) => !!active)
.map(([className]) => className);
}

return (
[...(vnode.data.staticClass?.split(' ') ?? []), ...dynamicClass].filter(Boolean).join(' ') ||
undefined
);
}

function stringifyAttr(attrName: string, value?: any): string | null {
if (typeof value === 'undefined' || typeof value === 'function') {
return null;
Expand Down

0 comments on commit fe2a84b

Please sign in to comment.