Subscribe on changes!

Vue 3 appends two text nodes for a slotted/template elements which breaks XPath selectors

avatar
May 28th 2023

Vue version

3.3.4

Link to minimal reproduction

https://codesandbox.io/s/hidden-water-0wm7te?file=/src/components/Slotter.vue

Steps to reproduce

  1. Open the link - https://codesandbox.io/s/hidden-water-0wm7te?file=/src/components/Slotter.vue
  2. See the alerted value (3)
  3. The value being alerted is the number if childNodes in the slotted element (which is plain text)

This causes issues with people who use XPath for their automation tests. For example try running this command in the devtools

document.evaluate("//*[contains(text(), 'testing')]", document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)

It will result in a null value. But if you delete and manually add the same text node using devtools it works because of the missing empty text nodes.

What is expected?

It should alert 1, there is only 1 node being slotted

What is actually happening?

It appends three text nodes, one surrounding the main text node each

image

System Info

No response

Any additional comments?

No response

avatar
May 28th 2023

From what I can see this happens because we use

hostInsert(fragmentStartAnchor, container, anchor)
hostInsert(fragmentEndAnchor, container, anchor)

I see why we need those anchors and I am unable to think of a better solution at the moment. My current "fix" is like so:

<template>
    <div v-if="slotTextValue"
        >{{ slotTextValue }}</div
    >
    <div v-else>
        <slot />
    </div>
</template>
<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
    computed: {
        slotTextValue() {
            const value = this.$slots.default?.();

            if (value?.length === 1 && typeof value[0].children === "string") {
                return value[0].children;
            }

            return null;
        },
    }
});
</script>
avatar
Jun 6th 2023

This is necessary for Vue to manage the fragments. "Fixing" this would require a massive refactor, and to be honest I do not believe it can be justified by "breaking XPath selectors". This sounds very specific to the way the test is written. You can assert text by getting .textContent of the parent element instead of asserting specific text nodes.