cannot run test with teleport

i have a set of related components that need to coexist to be tested. Everything in VDialog is wrapped in a Teleport internally. According to "https://test-utils.vuejs.org/guide/advanced/teleport", i can still use the find method on the mounted component to search for teleported components because it searches against the virtual dom. however this test keeps failing and for the life of me i can't get it to pass.
import { mountSuspended } from "@nuxt/test-utils/runtime";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import VDialogClose from "./dialog-close.vue";
import VDialogRoot from "./dialog-root.vue";
import VDialogTitle from "./dialog-title.vue";
import VDialogTrigger from "./dialog-trigger.vue";
import VDialog from "./dialog.vue";

const TestComponent = {
components: {
VDialogRoot,
VDialogTrigger,
VDialog,
VDialogTitle,
VDialogClose,
},
template: `
<VDialogRoot position="center">
<VDialogTrigger>
<button>Open Dialog</button>
</VDialogTrigger>
<VDialog>
<VDialogTitle>Dialog Heading</VDialogTitle>
<VDialogClose>
<button>Close</button>
</VDialogClose>
</VDialog>
</VDialogRoot>
`,
};

beforeAll(() => {
const teleportTarget = document.createElement("div");
teleportTarget.id = "teleports";
document.body.appendChild(teleportTarget);
});

afterAll(() => {
const teleportTarget = document.getElementById("teleports");
if (teleportTarget) {
document.body.removeChild(teleportTarget);
}
});

describe("dialog", () => {
it("has closed dialog on initial render", async () => {
const DialogRoot = await mountSuspended(TestComponent, { props: { position: "center" } });
const Dialog = DialogRoot.getComponent("VDialog");

expect(DialogRoot.find('[data-testid="dialog-trigger"]').exists()).toBe(true);
expect(Dialog.find('[data-testid="dialog"]').exists()).toBe(true);
});
});
import { mountSuspended } from "@nuxt/test-utils/runtime";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import VDialogClose from "./dialog-close.vue";
import VDialogRoot from "./dialog-root.vue";
import VDialogTitle from "./dialog-title.vue";
import VDialogTrigger from "./dialog-trigger.vue";
import VDialog from "./dialog.vue";

const TestComponent = {
components: {
VDialogRoot,
VDialogTrigger,
VDialog,
VDialogTitle,
VDialogClose,
},
template: `
<VDialogRoot position="center">
<VDialogTrigger>
<button>Open Dialog</button>
</VDialogTrigger>
<VDialog>
<VDialogTitle>Dialog Heading</VDialogTitle>
<VDialogClose>
<button>Close</button>
</VDialogClose>
</VDialog>
</VDialogRoot>
`,
};

beforeAll(() => {
const teleportTarget = document.createElement("div");
teleportTarget.id = "teleports";
document.body.appendChild(teleportTarget);
});

afterAll(() => {
const teleportTarget = document.getElementById("teleports");
if (teleportTarget) {
document.body.removeChild(teleportTarget);
}
});

describe("dialog", () => {
it("has closed dialog on initial render", async () => {
const DialogRoot = await mountSuspended(TestComponent, { props: { position: "center" } });
const Dialog = DialogRoot.getComponent("VDialog");

expect(DialogRoot.find('[data-testid="dialog-trigger"]').exists()).toBe(true);
expect(Dialog.find('[data-testid="dialog"]').exists()).toBe(true);
});
});
5 Replies
kapa.ai
kapa.ai3w ago
Hi @BinaryArtifex. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Your approach to testing teleported components in Nuxt (with Vitest and @nuxt/test-utils) is generally correct: you can use the find method to query teleported content, as the virtual DOM is searched, not just the rendered DOM. However, there are a few important details and recent changes in Nuxt and Vue testing that may affect your test: 1. Teleport Target: The recommended teleport target in Nuxt is now #teleports, not body. You are correctly creating and cleaning up this target in your test setup, which aligns with the latest Nuxt recommendations for teleportation targets in tests. This is important for ensuring teleported content is rendered where expected during tests, and matches the Nuxt fixture updates (see this PR). 2. Component Querying: When using find or getComponent, ensure that the selectors or component names match exactly what is rendered. If your VDialog component is a wrapper or functional component, or if it renders its content via teleport, you may need to adjust your queries. Also, if you are using custom data attributes (like data-testid), make sure these are present in the rendered output. 3. Nuxt Test Utils and Teleports: There are no explicit issues or limitations mentioned in the knowledge sources regarding using mountSuspended with teleports. The test-utils should handle teleports as long as the target exists and the component tree is mounted correctly. 4. Potential Issues: - If your test is failing on DialogRoot.getComponent("VDialog"), ensure that the component is registered and rendered as expected. - If the failure is on the find assertions, double-check that the data-testid attributes are present in your actual component templates. - If you are using Vuetify or another UI library that wraps dialogs in teleports, you may need to stub or mock certain transitions or components, as seen in other test setups (see this example). 5. Recent Nuxt Changes: There have been recent improvements and fixes to slot teleportation and client component teleportation in Nuxt, such as using useId for unique teleport targets and fixing issues with slots of the same name (see this PR, see this PR). If you are on an older version of Nuxt, consider updating to benefit from these fixes. Summary:
Your test setup for teleports is correct according to the latest Nuxt recommendations. If your test is still failing, double-check your component selectors and ensure your components and their data attributes are rendered as expected. If you are using a UI library, consider stubbing transitions or other wrappers that may interfere with mounting. There are no known issues in the knowledge sources that would prevent your test from working if these conditions are met. If you continue to have issues, please provide the specific error message or failure output for more targeted help. References:
- Nuxt PR: use '#teleports' target instead of 'body' - Nuxt PR: useId instead of build time hash for island client component teleport - Stack Overflow: Warning about wrong type (stubbing transitions) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@382140070183960576> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
dmarr
dmarr3w ago
Does it work if you use renderSuspended instead of mountSuspended ?
BinaryArtifex
BinaryArtifexOP3w ago
cheers for that mate, yeah that does seem to work. its a shame i loose out on the nice vue api that mount/mount-suspended provided for components. can't have it both ways for this kinda test i guess.
describe("dialog", () => {
it("has closed dialog on initial render", async () => {
const { findByTestId } = await renderSuspended(TestComponent);
const Dialog = await findByTestId("dialog");
const DialogTrigger = await findByTestId("dialog-trigger");

expect(DialogTrigger).instanceOf(HTMLElement);
expect(DialogTrigger.getAttribute("aria-expanded")).toEqual("false");
expect(DialogTrigger.getAttribute("aria-controls")).toEqual(Dialog.id);
expect(Dialog).instanceOf(HTMLElement);
expect(Dialog.getAttribute("open")).toBeFalsy();
});
});
describe("dialog", () => {
it("has closed dialog on initial render", async () => {
const { findByTestId } = await renderSuspended(TestComponent);
const Dialog = await findByTestId("dialog");
const DialogTrigger = await findByTestId("dialog-trigger");

expect(DialogTrigger).instanceOf(HTMLElement);
expect(DialogTrigger.getAttribute("aria-expanded")).toEqual("false");
expect(DialogTrigger.getAttribute("aria-controls")).toEqual(Dialog.id);
expect(Dialog).instanceOf(HTMLElement);
expect(Dialog.getAttribute("open")).toBeFalsy();
});
});
dmarr
dmarr3w ago
I mean that code looks better to me than the original haa

Did you find this page helpful?