-
-
Save mika76/bffe1eb7dbe8212f1d2c34be97fdcfb8 to your computer and use it in GitHub Desktop.
Vue 3 check for slot with no content
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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