-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BUG] Vue 3 Component Test Error: "ReferenceError: Item is not defined" #18346
Comments
Hi @StepanMynarik, good choice:) could you provide the test and the full component? |
I have made a full repro project. You can just:
|
Thanks for the repo and info! You seem to be right about the problem. I would consider writing the component in a different way or extracting To find out if the component can be written in a different way (which will probably be an improvement of the code anyway), i have to know a little more about what you are trying to solve with this piece of code?: // itemList.vue
<script lang="ts">
export class Item {
key: number | string;
label: string;
constructor(key: number | string, label: string) {
this.key = key;
this.label = label;
}
}
</script>
<script setup lang="ts">
const { items = [] } = defineProps<{
items: Item[];
}>();
</script>
<template>
<div class="vertical-list">
<div class="item" v-for="item in items" :key="item.key">
{{ item.label }}
</div>
</div>
</template> // itemList.spec.tsx
import ItemList, { Item } from "./ItemList.vue";
import { expect, test } from "@playwright/experimental-ct-vue";
import type { Locator } from "@playwright/test";
import type { Ref } from "vue";
const initialItemCount = 10;
const mockData = () => {
const itemsRaw: Item[] = new Array(initialItemCount);
for (let i = 0; i < initialItemCount; i++) {
itemsRaw[i] = new Item(i, `Item ${i + 1}`);
}
const items = $shallowRef(itemsRaw);
const selectedItems = $ref([items[1]]);
return $$({ items, selectedItems });
};
test.describe("<ItemList />", () => {
let mockedData: {
items: Ref<Item[]>;
selectedItems: Ref<Item[]>;
};
let component: Locator;
test.beforeEach(async ({ mount }) => {
mockedData = mockData();
component = await mount(
<ItemList
items={mockedData.items.value}
/>
);
});
test("draws items", async () => {
expect(component.locator(".item")).toHaveLength(initialItemCount);
});
}); |
Well I like the concept of SFCs. So when I need to create a highly reusable component like a multiselect for example, I can really use having a base Item class for anyone to use. This class can then have properties like "label", "key", "selected", "disabled" etc. that are used by the base component. Users of said component can then use just the base Item class, or they can even create an Full potential of TypeScript can also be unlocked by making the component generic (new feature of Vue 3) like so You see my point? Having the base Item class inside the component makes importing it extra easy. Also keeps project structure lean and clean. This was working in Cypress and was one of few things I liked about it. |
Thanks for the explanation! I see what you are what you are trying to do here. However IMO having a base class to extends from to create your props and solving this in an object oriented way doesn't seem the right approach to me. I'd just use plain old JavaScript objects instead (think you can also get this working with the new generic RFC as well). This makes the component easier to use and your tests easier to write: // ItemList.vue
<script lang="ts" setup>
type ItemListProps = {
items?: {
key: string | number;
label: string;
selected?: boolean;
[key: string]: any; // any other props
}[]
}
const { items = [] } = defineProps<ItemListProps>();
</script>
<template>
<div class="vertical-list">
<div data-testid="list-item" v-for="item in items" :key="item.key">
{{ item.label }}
</div>
</div>
</template> // ItemList.spec.tsx
import ItemList from "./ItemList.vue";
import { expect, test } from "@playwright/experimental-ct-vue";
test("render a list of items", async ({ mount }) => {
const component = await mount(<ItemList items={[
{ key: 1, label: '', anyOtherProp: 2 }, // NOTE: can add additional props
{ key: 2, label: '' }
]} />);
await expect(component.getByTestId("list-item")).toHaveCount(2);
}); |
Thank you for the suggestion. I come from the C# world, so I'm very picky about my "restrictive type contracts". And when I combine items of multiple types, I make a type guard and am done with it. Again, definitely doable with your technique, but then again. I'm so used to classes and keep switching between languages (for example ASP.NET based backend for my Vue app) so often that it's just better for me to stick with classes I believe. Srsly, I almost exclusively use classes for everything. It comes naturally to me. Yes, in JS, you can make check for keys being present on the object. But for example in this case I don't like it much. I also believe others would benefit from being able to import types from their vue files. Either from somewhat similar reasons to mine, preference, or because they don't know JS that well (also my case a bit I admit :D) |
I don't like the classes approach, but yeah many people will run into this problem. Until/if there is a solution these are your only two options:
What do you think of this @pavelfeldman? |
Thanks for having patience with me. I just like to keep my SFCs tight. I even use the custom block for localization. I can just grab any component and copy it into a separate repro/demo/any other project. Also in combination with <style scoped> block, you will never have useless css nor localization tuples leftovers again. I also enjoy not having to fish for any component related stuff. Any tips on stripping the spec.tsx files from the final build? |
I would not consider this limitation temporary. As you know, we are running tests in a different environment from the components. We've blurred the boundary as much as we could for better UX, but further blurring it would essentially eliminate that boundary. As a result, the test code that runs in Node will start running the component code that was intended to run within the browser. We will immediately start touching the global object (window), etc. |
I'm not that much of a JS world nerd, so I beg your pardon if what I say is BS. I use classes a lot in my props. And I always keep these short few line classes in my SFC components. I can extract the classes, but then I have to find a nice and sustainable way of organizing them in the project to not break my project structure (which is currently incredibly neat and I never have to think about it, it's incredibly easy to maintain). |
If all you need is a type, you should |
But I can't create an instance of the class from just the type. I would be ok with using an activator like in the bottom answer here for example, but that's not possible when the constructor doesn't actually exist in the test environment at runtime. If I understand it correctly. I could use interface instead of class, yes, but that isn't the same. You can't implement functions within interfaces, you can't have constructors that post-process passed arguments etc. I don't really care that much about some minor bloat within test files, or code cleanliness, so even some workaround would be welcome. If there is one. |
Why was this issue closed?Thank you for your contribution to our project. This issue has been closed due to its limited upvotes and recent activity, and insufficient feedback for us to effectively act upon. Our priority is to focus on bugs that reflect higher user engagement and have actionable feedback, to ensure our bug database stays manageable. Should you feel this closure was in error, please create a new issue and reference this one. We're open to revisiting it given increased support or additional clarity. Your understanding and cooperation are greatly appreciated. |
Context:
playwright test -c playwright-ct.config.ts
Hi,
I have just migrated from Cypress to Playwright and have encountered a similar issue to #17957 in Vue 3.
I'm defining an item type from withing my MultiSelect SFC component like so:
Then I just normally reference said type when mocking data for the component test.
This leads to
ReferenceError: Item is not defined
when running the test.The text was updated successfully, but these errors were encountered: