Skip to content
This repository has been archived by the owner on Mar 8, 2019. It is now read-only.

Commit

Permalink
feat: add slot support in jsx render function (#101)
Browse files Browse the repository at this point in the history
* add e2e tests and slotHandler first fixes

* slot support unit tests

* add description of slots in jsx

* fix e2e test

* Update src/script-handlers/slotHandler.ts

Co-Authored-By: elevatebart <ledouxb@me.com>

* fix code changes

* fix description true if no description at all

* last fixes
  • Loading branch information
elevatebart committed Feb 8, 2019
1 parent 821cdbc commit 8ad6f95
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 6 deletions.
73 changes: 72 additions & 1 deletion src/script-handlers/__tests__/slotHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('render function slotHandler', () => {

beforeEach(() => {
mockSlotDescriptor = { description: '' }
documentation = new (require('../../Documentation')).Documentation()
documentation = new Documentation()
const mockGetSlotDescriptor = documentation.getSlotDescriptor as jest.Mock
mockGetSlotDescriptor.mockReturnValue(mockSlotDescriptor)
})
Expand Down Expand Up @@ -55,4 +55,75 @@ describe('render function slotHandler', () => {
}
expect(documentation.getSlotDescriptor).toHaveBeenCalledWith('myScopedSlot')
})

it('should find scoped slots in render object method', () => {
const src = `
export default {
render(createElement) {
return createElement('div', [
this.$scopedSlots.myOtherScopedSlot({
text: this.message
})
])
}
}
`
const def = parse(src)
if (def) {
slotHandler(documentation, def)
}
expect(documentation.getSlotDescriptor).toHaveBeenCalledWith('myOtherScopedSlot')
})

it('should find slots in jsx render', () => {
const src = `
export default {
render(createElement) {
return (<div>,
<slot name="myMain"/>
</div>)
}
}
`
const def = parse(src)
if (def) {
slotHandler(documentation, def)
}
expect(documentation.getSlotDescriptor).toHaveBeenCalledWith('myMain')
})

it('should find default slots in jsx render', () => {
const src = `
export default {
render(createElement) {
return (<div>,
<slot/>
</div>)
}
}
`
const def = parse(src)
if (def) {
slotHandler(documentation, def)
}
expect(documentation.getSlotDescriptor).toHaveBeenCalledWith('default')
})

it('should allow describing slots in jsx render', () => {
const src = `
export default {
render(createElement) {
return (<div>,
{/** @slot Use this slot header */}
<slot/>
</div>)
}
}
`
const def = parse(src)
if (def) {
slotHandler(documentation, def)
}
expect(mockSlotDescriptor.description).toEqual('Use this slot header')
})
})
69 changes: 65 additions & 4 deletions src/script-handlers/slotHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as bt from '@babel/types'
import { NodePath } from 'ast-types'
import { Documentation, ParamTag, ParamType } from '../Documentation'
import { Documentation, ParamTag, ParamType, Tag } from '../Documentation'
import getDoclets from '../utils/getDoclets'

export interface TypedParamTag extends ParamTag {
type: ParamType
Expand All @@ -9,18 +10,24 @@ export interface TypedParamTag extends ParamTag {
// tslint:disable-next-line:no-var-requires
import recast = require('recast')

export default function eventHandler(documentation: Documentation, path: NodePath) {
export default function slotHandler(documentation: Documentation, path: NodePath) {
if (bt.isObjectExpression(path.node)) {
const renderPath = path
.get('properties')
.filter((p: NodePath) => bt.isObjectProperty(p.node) && p.node.key.name === 'render')
.filter(
(p: NodePath) =>
(bt.isObjectProperty(p.node) || bt.isObjectMethod(p.node)) &&
p.node.key.name === 'render',
)

// if no prop return
if (!renderPath.length) {
return
}

const renderValuePath = renderPath[0].get('value')
const renderValuePath = bt.isObjectProperty(renderPath[0].node)
? renderPath[0].get('value')
: renderPath[0]
recast.visit(renderValuePath, {
visitCallExpression(pathCall: NodePath<bt.CallExpression>) {
if (
Expand Down Expand Up @@ -48,6 +55,60 @@ export default function eventHandler(documentation: Documentation, path: NodePat
}
this.traverse(pathMember)
},
visitJSXElement(pathJSX: NodePath<bt.JSXElement>) {
const tagName = pathJSX.node.openingElement.name
if (bt.isJSXIdentifier(tagName) && tagName.name === 'slot') {
const doc = documentation.getSlotDescriptor(getName(pathJSX))
doc.description = getDescription(pathJSX)
}
this.traverse(pathJSX)
},
})
}
}

function getName(pathJSX: NodePath<bt.JSXElement>): string {
const oe = pathJSX.node.openingElement
const names = oe.attributes.filter(
(a: bt.JSXAttribute) => bt.isJSXAttribute(a) && a.name.name === 'name',
) as bt.JSXAttribute[]

const nameNode = names.length ? names[0].value : null
return nameNode && bt.isStringLiteral(nameNode) ? nameNode.value : 'default'
}

function getDescription(pathJSX: NodePath<bt.JSXElement>): string {
const siblings = (pathJSX.parentPath.node as bt.JSXElement).children
if (!siblings) {
return ''
}
const indexInParent = siblings.indexOf(pathJSX.node)

let commentExpression: bt.JSXExpressionContainer | null = null
for (let i = indexInParent - 1; i > -1; i--) {
const currentNode = siblings[i]
if (bt.isJSXExpressionContainer(currentNode)) {
commentExpression = currentNode
break
}
}
if (!commentExpression || !commentExpression.expression.innerComments) {
return ''
}
const cmts = commentExpression.expression.innerComments
const lastComment = cmts[cmts.length - 1]
if (lastComment.type !== 'CommentBlock') {
return ''
}
const docBlock = lastComment.value.replace(/^\*/, '').trim()
const jsDoc = getDoclets(docBlock)
if (!jsDoc.tags) {
return ''
}
const slotTags = jsDoc.tags.filter(t => t.title === 'slot')
if (slotTags.length) {
const tagContent = (slotTags[0] as Tag).content
return typeof tagContent === 'string' ? tagContent : ''
}
return ''
}
2 changes: 2 additions & 0 deletions tests/components/grid-jsx/Grid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export default {
const { sortKey, capitalize } = this
return (
<table class="grid">
{/** @slot Use this slot header */}
<slot name="header" />
<thead>
<tr>
{columns.map(key => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ Object {
},
},
},
"slots": Object {},
"slots": Object {
"header": Object {
"description": "Use this slot header",
},
},
"tags": Object {
"author": Array [
Object {
Expand Down
4 changes: 4 additions & 0 deletions tests/components/grid-jsx/grid-jsx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ describe('tests grid jsx', () => {
expect(docGrid.methods[0].returns).toMatchObject({ description: 'Test' })
})

it('should return slots from the render method', () => {
expect(docGrid.slots.header).toMatchObject({ description: 'Use this slot header' })
})

it('should match the snapshot', () => {
expect(docGrid).toMatchSnapshot()
})
Expand Down

0 comments on commit 8ad6f95

Please sign in to comment.