Skip to content

Instantly share code, notes, and snippets.

@mika76
Forked from marina-mosti/useEmptySlotCheck.js
Created May 6, 2023 11:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mika76/bffe1eb7dbe8212f1d2c34be97fdcfb8 to your computer and use it in GitHub Desktop.
Save mika76/bffe1eb7dbe8212f1d2c34be97fdcfb8 to your computer and use it in GitHub Desktop.
Vue 3 check for slot with no content
import { computed, Comment, Fragment, Text } from 'vue'
/**
* Determines whether a slot is empty for Vue 3: https://github.com/vuejs/vue-next/issues/3056
* @param {Function} slot - The slot $slot.name
* @returns {Boolean}
*/
// Adapted from https://github.com/vuejs/vue-next/blob/ca17162e377e0a0bf3fae9d92d0fdcb32084a9fe/packages/runtime-core/src/helpers/renderSlot.ts#L77
function vNodeIsEmpty (vnodes) {
return vnodes.every(node => {
if (node.type === Comment) return true
if (node.type === Text && !node.children.trim()) return true
if (
node.type === Fragment &&
vNodeIsEmpty(node.children)
) {
return true
}
return false
})
}
/**
* Returns true if a slot has no content
* @param {Function | Object} slot a Vue 3 slot function or a Vue 2 slot object
* @returns {Boolean}
*/
export const isEmpty = slot => {
if (!slot) return true
// if we get a slot that is not a function, we're in vue 2 and there is content, so it's not empty
if (typeof slot !== 'function') return false
return vNodeIsEmpty(slot())
}
export default function ({
slot
}) {
const slotIsEmpty = computed(() => isEmpty(slot))
return {
slotIsEmpty
}
}
import useEmptySlotCheck from './useEmptySlotCheck'
import { mount } from '@vue/test-utils'
import { ref } from 'vue'
const ComponentWithSlots = {
name: 'ComponentWithSlots',
template: `
<div>
Default:
<slot />
Named:
<slot name="namedSlot" />
Empty:
<slot name="leaveEmpty" />
Empty string:
<slot name="emptyString" />
Comment:
<slot name="comment" />
Fragment:
<slot name="fragment" />
<p v-if="defaultIsEmpty">Default is empty</p>
<p v-if="namedIsEmpty">Named is empty</p>
</div>
`,
setup (_, { slots }) {
const { slotIsEmpty: defaultIsEmpty } = useEmptySlotCheck({ slot: slots.default })
const { slotIsEmpty: namedIsEmpty } = useEmptySlotCheck({ slot: slots.namedSlot })
return {
defaultIsEmpty,
namedIsEmpty
}
}
}
function factory (opts = {}) {
return mount({
name: 'ComponentUsingSlots',
components: { ComponentWithSlots },
template: opts.template,
setup: opts.setup ? opts.setup : () => ({})
})
}
let wrapper
describe('useEmptySlotCheck', () => {
beforeEach(() => {
jest.clearAllMocks()
})
afterEach(() => {
if (wrapper) wrapper.unmount()
})
describe('identifying empty slots', () => {
describe('default slots', () => {
it('marks the default slot as empty when it has no content', async () => {
const template = `
<ComponentWithSlots>
<template #default></template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Default is empty')
})
it('marks the default slot as not empty when it has content', async () => {
const template = `
<ComponentWithSlots>
<template #default>
Some content!
</template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).not.toContain('Default is empty')
})
})
describe('named slots', () => {
it('marks a named slot as empty when it has no content', async () => {
const template = `
<ComponentWithSlots>
<template #namedSlot></template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Named is empty')
})
it('marks a named slot as not empty when it has content', async () => {
const template = `
<ComponentWithSlots>
<template #namedSlot>
Some named content!
</template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).not.toContain('Named is empty')
})
})
describe('other empty conditions', () => {
it('marks a slot as empty if the parent puts nothing in the slot', async () => {
const template = `
<ComponentWithSlots />
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Named is empty')
})
it('marks a slot as empty if the slot only contains whitespace', async () => {
const template = `
<ComponentWithSlots>
<template #namedSlot>
</template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Named is empty')
})
it('marks a slot as empty if the slot only contains a comment', async () => {
const template = `
<ComponentWithSlots>
<template #namedSlot>
<!-- Comment -->
</template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Named is empty')
})
it('marks a slot as empty if the slot only contains another empty slot', async () => {
const template = `
<ComponentWithSlots>
<template #namedSlot>
<slot name="anotherSlot" />
</template>
</ComponentWithSlots>
`
wrapper = factory({ template })
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Named is empty')
})
})
})
describe('handling changes to slot content', () => {
it('marks a slot as empty when it becomes empty', async () => {
const template = `
<ComponentWithSlots>
<template #default>
<div v-if="showSlotContent">Slot content!</div>
</template>
</ComponentWithSlots>
`
const showSlotContent = ref(true)
wrapper = factory({
template,
setup () {
return { showSlotContent }
}
})
expect(wrapper.text()).not.toContain('Default is empty')
showSlotContent.value = false
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Default is empty')
})
it('marks the slot as not empty when it gets content', async () => {
const template = `
<ComponentWithSlots>
<template #default>
<div v-if="showSlotContent">Slot content!</div>
</template>
</ComponentWithSlots>
`
const showSlotContent = ref(false)
wrapper = factory({
template,
setup () {
return { showSlotContent }
}
})
expect(wrapper.text()).toContain('Default is empty')
showSlotContent.value = true
await wrapper.vm.$nextTick()
expect(wrapper.text()).not.toContain('Default is empty')
})
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment