Manually trigger click on ion-button using vitest

I’m trying to unit test this component.

<template>
  <form @submit.prevent="handleSubmit">
    <div>
      <ion-input
        label="Email"
        data-testid="email-input"
        name="email"
        label-placement="floating"
        fill="solid"
        v-model="form.email"
        type="email"
        required
      />
    </div>

    <div>
      <label for="username">Username</label>
      <input
        id="username"
        name="username"
        v-model="form.username"
        type="text"
        required
      />
    </div>

    <div>
      <label for="password">Password</label>
      <input
        id="password"
        name="password"
        v-model="form.password"
        type="password"
        required
      />
    </div>

    <ion-button type="submit" :disabled="isSubmitting">
      {{ isSubmitting ? "Saving..." : "Submit" }}
    </ion-button>
  </form>
</template>

<script setup lang="ts">
import { IonInput, IonButton } from "@ionic/vue";
import { reactive, ref, watch } from "vue";

interface UserFormData {
  email: string;
  username: string;
  password: string;
}

const props = defineProps<{
  user?: Partial<UserFormData>;
  saving: boolean;
}>();

const emit = defineEmits<{
  (e: "submit", data: UserFormData): void;
}>();

const defaultUser: UserFormData = {
  email: "",
  username: "",
  password: "",
};

const initialState = (): UserFormData => ({
  ...defaultUser,
  ...props.user,
});

const form = reactive<UserFormData>(initialState());
const isSubmitting = ref(false);

function handleSubmit() {
  isSubmitting.value = true;
  emit("submit", { ...form });
}

function resetForm() {
  Object.assign(form, initialState());
}

watch(
  () => props.saving,
  (newVal, oldVal) => {
    if (oldVal && !newVal) {
      isSubmitting.value = false;
    }
  }
);

watch(
  () => props.user,
  () => {
    Object.assign(form, initialState());
  }
);

defineExpose({ resetForm });
</script>

For some reason, the click event is not triggering on the ion-button:

import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import UserForm from "@/components/UserForm.vue";

describe("UserForm.vue", () => {
  it("submits the form", async () => {
    const wrapper = mount(UserForm, { props: { saving: false }, attachTo: document.body });
    await wrapper.findComponent("[data-testid=email-input]").setValue("email@example.com");
    await wrapper.find("input[name=username]").setValue("username");
    await wrapper.find("input[name=password]").setValue("passwordf");
    const submitButton = wrapper.findComponent("ion-button");
    await submitButton.trigger("click");

    expect(wrapper.emitted()).toHaveProperty("submit");
    expect(submitButton.attributes("disabled")).toBeDefined();
    wrapper.unmount();
  });
});

expected { input: [ [ …(1) ], [ …(1) ] ], …(2) } to have property "submit"

Using the native <button> the test passes, but with <ion-button> it fails.

How do I properly manually trigger the click event?

Can you find the HTML button using the native shadow part?

Something like:

const submitButton = wrapper.find("ion-button::part(native)")

With jsdom it fails with unknown pseudo-class selector '::part(native)'.

Using happy-dom it breaks when calling submitButton.trigger("click").

happy-dom stack
⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
TypeError: Cannot read properties of undefined (reading 'replace')
 ❯ Function.parseFromString node_modules/happy-dom/src/css/utilities/CSSParser.ts:29:23
 ❯ CSSStyleSheet.replaceSync node_modules/happy-dom/src/css/CSSStyleSheet.ts:137:43
 ❯ registerStyle node_modules/@stencil/core/internal/client/index.js:2146:13
    2144|       style = cssText;
    2145|     } else {
    2146|       style.replaceSync(cssText);
       |             ^
    2147|     }
    2148|   } else {
 ❯ initializeComponent node_modules/@stencil/core/internal/client/index.js:3594:9
 ❯ connectedCallback node_modules/@stencil/core/internal/client/index.js:3665:9
 ❯ Button.connectedCallback node_modules/@stencil/core/internal/client/index.js:3779:7
 ❯ CustomElementReactionStack.enqueueReaction node_modules/happy-dom/src/custom-element/CustomElementReactionStack.ts:52:80
 ❯ Button.[connectedToDocument] node_modules/happy-dom/src/nodes/element/Element.ts:1502:74
 ❯ Button.[connectedToNode] node_modules/happy-dom/src/nodes/node/Node.ts:992:44
 ❯ Button.[connectedToNode] node_modules/happy-dom/src/nodes/html-element/HTMLElement.ts:1008:40

This error originated in "tests/unit/UserForm.spec.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯