diff --git a/eslint.config.mjs b/eslint.config.mjs index 15804c42ac4..b46aa7228c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -54,8 +54,6 @@ const eslintIgnores = [ "frontend/.output/**", "frontend/.remake/**", "frontend/src/locales/*.json", - // Vendored module. See explanation in file - "frontend/test/unit/test-utils/render-suspended.ts", "**/coverage/**", "frontend/test/tapes/**", "frontend/storybook-static/**", diff --git a/frontend/src/components/VButton.vue b/frontend/src/components/VButton.vue index c7990ec7fb2..9a9bb6d756e 100644 --- a/frontend/src/components/VButton.vue +++ b/frontend/src/components/VButton.vue @@ -191,6 +191,10 @@ const disabledAttribute = computed(() => { return (trulyDisabled && supportsDisabledAttribute.value) || undefined }) +const linkProps = computed(() => + props.as === "VLink" ? { href: attrs.href } : {} +) + watch( () => props.as, (as) => { @@ -229,6 +233,7 @@ watch( :aria-pressed="pressed" :aria-disabled="ariaDisabled" :disabled="disabledAttribute" + v-bind="linkProps" @click="$emit('click', $event)" @mousedown="$emit('mousedown', $event)" @keydown="$emit('keydown', $event)" diff --git a/frontend/src/components/VHeader/VHomeLink.vue b/frontend/src/components/VHeader/VHomeLink.vue index 9abcc1dbeef..41421cbb812 100644 --- a/frontend/src/components/VHeader/VHomeLink.vue +++ b/frontend/src/components/VHeader/VHomeLink.vue @@ -15,7 +15,7 @@ withDefaults( * * @default 'light' */ - variant: "light" | "dark" + variant?: "light" | "dark" }>(), { variant: "light" } ) diff --git a/frontend/src/components/VHeader/VWordPressLink.vue b/frontend/src/components/VHeader/VWordPressLink.vue index bb44b26e1bc..b810ca6f741 100644 --- a/frontend/src/components/VHeader/VWordPressLink.vue +++ b/frontend/src/components/VHeader/VWordPressLink.vue @@ -4,7 +4,7 @@ import VSvg from "~/components/VSvg/VSvg.vue" withDefaults( defineProps<{ - mode: "dark" | "light" + mode?: "dark" | "light" }>(), { mode: "light" } ) diff --git a/frontend/src/components/VLogoLoader/VLogoLoader.vue b/frontend/src/components/VLogoLoader/VLogoLoader.vue index 74bf106bcb0..7a30b68a6af 100644 --- a/frontend/src/components/VLogoLoader/VLogoLoader.vue +++ b/frontend/src/components/VLogoLoader/VLogoLoader.vue @@ -3,7 +3,7 @@ import { useReducedMotion } from "~/composables/use-reduced-motion" withDefaults( defineProps<{ - status: "loading" | "idle" + status?: "loading" | "idle" }>(), { status: "idle", diff --git a/frontend/src/components/VTag/VTag.vue b/frontend/src/components/VTag/VTag.vue index f2898b6ca3c..a57222b4e27 100644 --- a/frontend/src/components/VTag/VTag.vue +++ b/frontend/src/components/VTag/VTag.vue @@ -1,7 +1,7 @@ diff --git a/frontend/test/unit/specs/components/AudioTrack/v-audio-track.spec.js b/frontend/test/unit/specs/components/AudioTrack/v-audio-track.spec.js index 8ed7bc01704..d9913ae81f3 100644 --- a/frontend/test/unit/specs/components/AudioTrack/v-audio-track.spec.js +++ b/frontend/test/unit/specs/components/AudioTrack/v-audio-track.spec.js @@ -1,10 +1,8 @@ import { fireEvent } from "@testing-library/vue" -import { createApp } from "vue" +import { createApp, nextTick } from "vue" import { render } from "~~/test/unit/test-utils/render" - -import { i18n } from "~~/test/unit/test-utils/i18n" import { getAudioObj } from "~~/test/unit/fixtures/audio" import { useActiveMediaStore } from "~/stores/active-media" @@ -24,6 +22,7 @@ const RouterLinkStub = createApp({}).component("RouterLink", { }, }, })._context.components.RouterLink + const stubs = { VLicense: true, VWaveform: true, @@ -31,10 +30,23 @@ const stubs = { RouterLink: RouterLinkStub, } +const captureExceptionMock = vi.fn() + +vi.mock("#app", async () => { + const original = await import("#app") + return { + ...original, + useNuxtApp: vi.fn(() => ({ + $sentry: { + captureException: captureExceptionMock, + }, + })), + } +}) + describe("AudioTrack", () => { let options = null let props = null - const captureExceptionMock = vi.fn() beforeEach(() => { props = { @@ -54,7 +66,6 @@ describe("AudioTrack", () => { options = { props: props, global: { - plugins: [i18n], stubs, }, } @@ -94,8 +105,7 @@ describe("AudioTrack", () => { expect(creator).toBeTruthy() }) - // https://github.com/wordpress/openverse/issues/411 - it.skip.each` + it.each` errorType | errorText ${"NotAllowedError"} | ${/Reproduction not allowed./i} ${"NotSupportedError"} | ${/This audio format is not supported by your browser./i} @@ -111,19 +121,22 @@ describe("AudioTrack", () => { vi.clearAllMocks() - const pauseStub = vi - .spyOn(window.HTMLMediaElement.prototype, "pause") - .mockImplementation(() => undefined) - + const pauseStub = vi.fn(() => undefined) + const playStub = vi.fn(() => Promise.reject(playError)) const playError = new DOMException("msg", errorType) - const playStub = vi - .spyOn(window.HTMLMediaElement.prototype, "play") - .mockImplementation(() => Promise.reject(playError)) + vi.spyOn(window.HTMLMediaElement.prototype, "pause").mockImplementation( + pauseStub + ) + + vi.spyOn(window.HTMLMediaElement.prototype, "play").mockImplementation( + playStub + ) const { getByRole, getByText } = await render(VAudioTrack, options) - await fireEvent.click(getByRole("button")) + await fireEvent.click(getByRole("button", { name: /play/i })) + await nextTick() expect(playStub).toHaveBeenCalledTimes(1) expect(pauseStub).toHaveBeenCalledTimes(1) expect(getByText(errorText)).toBeVisible() diff --git a/frontend/test/unit/specs/components/ImageDetails/v-copy-license.spec.js b/frontend/test/unit/specs/components/ImageDetails/v-copy-license.spec.js index a7ee333dfe9..3b37d056a4b 100644 --- a/frontend/test/unit/specs/components/ImageDetails/v-copy-license.spec.js +++ b/frontend/test/unit/specs/components/ImageDetails/v-copy-license.spec.js @@ -22,7 +22,6 @@ describe("VCopyLicense", () => { creator_url: "http://creator.com", frontendMediaType: "image", }, - fullLicenseName: "LICENSE", } options = { props } }) diff --git a/frontend/test/unit/specs/components/InputField/input-field.spec.js b/frontend/test/unit/specs/components/InputField/input-field.spec.js index 075f71d69f4..57cb86a3c49 100644 --- a/frontend/test/unit/specs/components/InputField/input-field.spec.js +++ b/frontend/test/unit/specs/components/InputField/input-field.spec.js @@ -9,7 +9,6 @@ import VInputField from "~/components/VInputField/VInputField.vue" const props = { fieldId: "input-id", labelText: "Label", - size: "medium", } describe("VInputField", () => { diff --git a/frontend/test/unit/specs/components/VMediaInfo/v-media-details.spec.js b/frontend/test/unit/specs/components/VMediaInfo/v-media-details.spec.js index 86d0a543d4c..c1c46aaac7b 100644 --- a/frontend/test/unit/specs/components/VMediaInfo/v-media-details.spec.js +++ b/frontend/test/unit/specs/components/VMediaInfo/v-media-details.spec.js @@ -28,7 +28,7 @@ describe("VMediaDetails", () => { } }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("renders the album title", async () => { await render(VMediaDetails, options) @@ -39,27 +39,27 @@ describe("VMediaDetails", () => { ) }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("hides the album title tag when it does not exists", async () => { options.props.media.audio_set = null await render(VMediaDetails, options) expect(screen.queryByText("Album")).toBeNull() }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("displays the main filetype when no alternative files are available", async () => { await render(VMediaDetails, options) expect(screen.queryByText("MP32")).toBeVisible() }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("displays multiple filetypes when they are available in alt_files", async () => { options.props.media.alt_files = [{ filetype: "wav" }, { filetype: "ogg" }] await render(VMediaDetails, options) expect(screen.queryByText("MP32, WAV, OGG")).toBeVisible() }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("displays only distinct filetypes", async () => { options.props.media.alt_files = [{ filetype: "ogg" }, { filetype: "ogg" }] await render(VMediaDetails, options) diff --git a/frontend/test/unit/specs/components/VSafetyWall/v-safety-wall.spec.js b/frontend/test/unit/specs/components/VSafetyWall/v-safety-wall.spec.js index 378cc54fea7..ce083132b23 100644 --- a/frontend/test/unit/specs/components/VSafetyWall/v-safety-wall.spec.js +++ b/frontend/test/unit/specs/components/VSafetyWall/v-safety-wall.spec.js @@ -4,8 +4,6 @@ import { createApp } from "vue" import { render } from "~~/test/unit/test-utils/render" -import { i18n } from "~~/test/unit/test-utils/i18n" - import { useSearchStore } from "~/stores/search" import VSafetyWall from "~/components/VSafetyWall/VSafetyWall.vue" @@ -25,17 +23,15 @@ describe("VSafetyWall.vue", () => { beforeEach(() => { options = { global: { - plugins: [i18n], stubs: { RouterLink: RouterLinkStub }, }, props: { - media: { - sensitivity: [ - "sensitive_text", - "provider_supplied_sensitive", - "user_reported_sensitive", - ], - }, + sensitivity: [ + "sensitive_text", + "provider_supplied_sensitive", + "user_reported_sensitive", + ], + id: "123", }, } }) diff --git a/frontend/test/unit/specs/components/VTag/v-tag.spec.js b/frontend/test/unit/specs/components/VTag/v-tag.spec.js index cc5303ea240..fffb8ed7f49 100644 --- a/frontend/test/unit/specs/components/VTag/v-tag.spec.js +++ b/frontend/test/unit/specs/components/VTag/v-tag.spec.js @@ -21,10 +21,11 @@ describe("VTag", () => { expect(link.href).toEqual("https://example.com/") }) - // https://github.com/wordpress/openverse/issues/411 - it.skip("renders slot content", async () => { + it("renders slot content", async () => { const slotText = "Slot test" - options.slots = { default: () => `
${slotText}
` } + // Using a non-function value for the slot causes a Vue warning, + // but a function value is not rendered correctly by the unit tests. + options.slots = { default: `
${slotText}
` } await render(VTag, options) expect(screen.getByText(slotText)).toBeDefined() diff --git a/frontend/test/unit/specs/components/scroll-button.spec.js b/frontend/test/unit/specs/components/scroll-button.spec.js index a7b1a6ddea5..400a9722f62 100644 --- a/frontend/test/unit/specs/components/scroll-button.spec.js +++ b/frontend/test/unit/specs/components/scroll-button.spec.js @@ -2,15 +2,11 @@ import { screen } from "@testing-library/vue" import { render } from "~~/test/unit/test-utils/render" -import { i18n } from "~~/test/unit/test-utils/i18n" - import VScrollButton from "~/components/VScrollButton.vue" describe("Scroll button", () => { it("should render a scroll button", async () => { - const { container } = await render(VScrollButton, { - global: { plugins: [i18n] }, - }) + const { container } = await render(VScrollButton) expect(screen.getByRole("button")).toBeTruthy() expect(screen.getByLabelText(/scroll/i)).toBeTruthy() expect(container.querySelectorAll("svg").length).toEqual(1) diff --git a/frontend/test/unit/specs/components/v-content-link.spec.js b/frontend/test/unit/specs/components/v-content-link.spec.js index 72a57241848..ebd14dbccea 100644 --- a/frontend/test/unit/specs/components/v-content-link.spec.js +++ b/frontend/test/unit/specs/components/v-content-link.spec.js @@ -6,8 +6,6 @@ import { createApp } from "vue" import { render } from "~~/test/unit/test-utils/render" -import { i18n } from "~~/test/unit/test-utils/i18n" - import VContentLink from "~/components/VContentLink/VContentLink.vue" const RouterLinkStub = createApp({}).component("RouterLink", { @@ -26,7 +24,6 @@ describe("VContentLink", () => { beforeEach(() => { options = { global: { - plugins: [i18n], stubs: { RouterLink: RouterLinkStub }, }, props: { diff --git a/frontend/test/unit/specs/components/v-image-cell.spec.js b/frontend/test/unit/specs/components/v-image-cell.spec.js index 6e5b8ec22b3..f841e922c93 100644 --- a/frontend/test/unit/specs/components/v-image-cell.spec.js +++ b/frontend/test/unit/specs/components/v-image-cell.spec.js @@ -2,7 +2,6 @@ import { createApp } from "vue" import { image } from "~~/test/unit/fixtures/image" import { render } from "~~/test/unit/test-utils/render" -import { i18n } from "~~/test/unit/test-utils/i18n" import VImageCell from "~/components/VImageCell/VImageCell.vue" @@ -21,13 +20,13 @@ describe("VImageCell", () => { beforeEach(() => { options = { global: { - plugins: [i18n], stubs: { RouterLink: RouterLinkStub, }, }, props: { image, + kind: "search", searchTerm: "cat", relatedTo: null, }, diff --git a/frontend/test/unit/specs/utils/api-token/api-token-missing-credentials.spec.js b/frontend/test/unit/specs/utils/api-token/api-token-missing-credentials.spec.js index f8120f53d11..5a493be65b1 100644 --- a/frontend/test/unit/specs/utils/api-token/api-token-missing-credentials.spec.js +++ b/frontend/test/unit/specs/utils/api-token/api-token-missing-credentials.spec.js @@ -4,7 +4,7 @@ import { getApiAccessToken } from "~/plugins/01.api-token.server" describe("missing client credentials", () => { describe("completely missing", () => { - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should not make any requests and fall back to tokenless", async () => { const token = await getApiAccessToken() @@ -13,7 +13,7 @@ describe("missing client credentials", () => { }) describe("explicitly undefined", () => { - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should not make any requests and fall back to tokenless", async () => { const accessToken = await getApiAccessToken({ apiClientId: undefined, diff --git a/frontend/test/unit/specs/utils/api-token/api-token-parallel.spec.js b/frontend/test/unit/specs/utils/api-token/api-token-parallel.spec.js index b1bed2986e3..2ebf43125b3 100644 --- a/frontend/test/unit/specs/utils/api-token/api-token-parallel.spec.js +++ b/frontend/test/unit/specs/utils/api-token/api-token-parallel.spec.js @@ -33,7 +33,7 @@ vi.mock("#app/nuxt", async () => { } }) -// https://github.com/wordpress/openverse/issues/411 +// https://github.com/wordpress/openverse/issues/5171 it.skip("subsequent requests should all block on the same token retrieval promise", async () => { /** * This test is pretty complicated because we need to simulate diff --git a/frontend/test/unit/specs/utils/api-token/api-token-successful.spec.js b/frontend/test/unit/specs/utils/api-token/api-token-successful.spec.js index 83c29f36b2d..1950aa96a09 100644 --- a/frontend/test/unit/specs/utils/api-token/api-token-successful.spec.js +++ b/frontend/test/unit/specs/utils/api-token/api-token-successful.spec.js @@ -51,7 +51,7 @@ describe.sequential("api-token", () => { }) describe("successful token retrieval", () => { - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should save the token into the process and inject into the context", async () => { const mockTokenResponse = getMockTokenResponse() axios.post.mockImplementationOnce(() => @@ -67,7 +67,7 @@ describe.sequential("api-token", () => { }) }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should re-retrieve the token when about to expire", async () => { const mockTokenResponse = getMockTokenResponse(expiryThreshold - 1) const nextMockTokenResponse = getMockTokenResponse() @@ -89,7 +89,7 @@ describe.sequential("api-token", () => { }) }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should not request a new token if the token is not about to expire", async () => { const mockTokenResponse = getMockTokenResponse(twelveHoursInSeconds) const nextMockTokenResponse = getMockTokenResponse() diff --git a/frontend/test/unit/specs/utils/api-token/api-token-unsuccessful.spec.js b/frontend/test/unit/specs/utils/api-token/api-token-unsuccessful.spec.js index 1c544324b35..1eb2a4ca2e3 100644 --- a/frontend/test/unit/specs/utils/api-token/api-token-unsuccessful.spec.js +++ b/frontend/test/unit/specs/utils/api-token/api-token-unsuccessful.spec.js @@ -49,7 +49,7 @@ describe("unsuccessful token retrieval", () => { } process.tokenFetching = defaultPromise }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should empty the token data", async () => { const app = useNuxtApp() const firstTokenResponse = getMockTokenResponse(expiryThreshold - 1) @@ -69,7 +69,7 @@ describe("unsuccessful token retrieval", () => { expect(token2).toEqual("") }) - // https://github.com/wordpress/openverse/issues/411 + // https://github.com/wordpress/openverse/issues/5171 it.skip("should properly release the mutex and allow for subsequent requests to retry the token refresh", async () => { const firstTokenResponse = getMockTokenResponse(expiryThreshold - 1) const finalTokenResponse = getMockTokenResponse() diff --git a/frontend/test/unit/test-utils/i18n.js b/frontend/test/unit/test-utils/i18n.js index 9fb45fbd434..74d6f80ca92 100644 --- a/frontend/test/unit/test-utils/i18n.js +++ b/frontend/test/unit/test-utils/i18n.js @@ -1,5 +1,7 @@ import { createI18n } from "vue-i18n" +import { computed } from "vue" + import messages from "~/locales/en.json" const globalizedI18n = () => { @@ -12,7 +14,7 @@ const globalizedI18n = () => { }, }) i18n.t = i18n.global.t - i18n.localeProperties = { code: "en", dir: "ltr" } + i18n.localeProperties = computed(() => ({ code: "en", dir: "ltr" })) return i18n } diff --git a/frontend/test/unit/test-utils/render-suspended.ts b/frontend/test/unit/test-utils/render-suspended.ts deleted file mode 100644 index b649f2538df..00000000000 --- a/frontend/test/unit/test-utils/render-suspended.ts +++ /dev/null @@ -1,184 +0,0 @@ -// Copied from https://github.com/nuxt/test-utils/blob/bc86d23b2cf92aa3c3f19d52944452e4e5c27d63/src/runtime-utils/render.ts -// Fixes the issue when values returned from setup are not available in template when using renderSuspended -// Line 154: replaced `render(renderContext, ...args)` with `render(_ctx, ...args)` - -import { - type DefineComponent, - type SetupContext, - defineComponent, - Suspense, - h, - nextTick, -} from "vue" - -import { defu } from "defu" - -import type { RenderOptions as TestingLibraryRenderOptions } from "@testing-library/vue" -import type { RouteLocationRaw } from "vue-router" - -export const RouterLink = defineComponent({ - functional: true, - props: { - to: { - type: [String, Object], - required: true, - }, - custom: Boolean, - replace: Boolean, - // Not implemented - activeClass: String, - exactActiveClass: String, - ariaCurrentValue: String, - }, - setup: (props, { slots }) => { - const navigate = () => {} - return () => { - const route = useRouter().resolve(props.to) - - return props.custom - ? slots.default?.({ href: route.href, navigate, route }) - : h( - "a", - { - href: route.href, - onClick: (e: MouseEvent) => { - e.preventDefault() - return navigate() - }, - }, - slots - ) - } - }, -}) - -import NuxtRoot from "#build/root-component.mjs" - -import { useRouter } from "#imports" - -export type RenderOptions = TestingLibraryRenderOptions & { - route?: RouteLocationRaw -} - -export const WRAPPER_EL_ID = "test-wrapper" - -/** - * `renderSuspended` allows you to mount any vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins. - * - * This is a wrapper around the `render` function from @testing-libary/vue, and should be used together with - * utilities from that package. - * - * ```ts - * // tests/components/SomeComponents.nuxt.spec.ts - * import { renderSuspended } from '@nuxt/test-utils/runtime' - * - * it('can render some component', async () => { - * const { html } = await renderSuspended(SomeComponent) - * expect(html()).toMatchInlineSnapshot( - * 'This is an auto-imported component' - * ) - * - * }) - * - * // tests/App.nuxt.spec.ts - * import { renderSuspended } from '@nuxt/test-utils/runtime' - * import { screen } from '@testing-library/vue' - * - * it('can also mount an app', async () => { - * const { html } = await renderSuspended(App, { route: '/test' }) - * expect(screen.getByRole('link', { name: 'Test Link' })).toBeVisible() - * }) - * ``` - * @param component the component to be tested - * @param options optional options to set up your component - */ -export async function renderSuspended( - component: T, - options?: RenderOptions -) { - const { - props = {}, - attrs = {}, - slots = {}, - route = "/", - ..._options - } = options || {} - - const { render: renderFromTestingLibrary } = await import( - "@testing-library/vue" - ) - - // @ts-expect-error untyped global __unctx__ - const { vueApp } = globalThis.__unctx__.get("nuxt-app").tryUse() - const { render, setup } = component as DefineComponent - - // cleanup previously mounted test wrappers - document.querySelector(`#${WRAPPER_EL_ID}`)?.remove() - - let setupContext: SetupContext - - return new Promise>((resolve) => { - const utils = renderFromTestingLibrary( - { - // eslint-disable-next-line @typescript-eslint/no-shadow - setup: (props: any, ctx: any) => { - setupContext = ctx - - return NuxtRoot.setup(props, { - ...ctx, - expose: () => {}, - }) - }, - render: () => - // See discussions in https://github.com/testing-library/vue-testing-library/issues/230 - // we add this additional root element because otherwise testing-library breaks - // because there's no root element while Suspense is resolving - h( - "div", - { id: WRAPPER_EL_ID }, - h( - Suspense, - { onResolve: () => nextTick().then(() => resolve(utils)) }, - { - default: () => - h({ - async setup() { - const router = useRouter() - await router.replace(route) - - // Proxy top-level setup/render context so test wrapper resolves child component - const clonedComponent = { - ...component, - - // TODO: Should it be render({{ ...ctx, ...renderContext }}) instead? - render: render - ? (_ctx: any, ...args: any[]) => render(_ctx, ...args) - : undefined, - setup: setup - ? // eslint-disable-next-line @typescript-eslint/no-shadow - (props: Record) => - setup(props, setupContext) - : undefined, - } - - return () => - h(clonedComponent, { ...props, ...attrs }, slots) - }, - }), - } - ) - ), - }, - defu(_options, { - slots, - global: { - config: { - globalProperties: vueApp.config.globalProperties, - }, - provide: vueApp._context.provides, - components: { RouterLink }, - }, - }) - ) - }) -} diff --git a/frontend/test/unit/test-utils/render.js b/frontend/test/unit/test-utils/render.js index b5f9f100a96..afe08f3879c 100644 --- a/frontend/test/unit/test-utils/render.js +++ b/frontend/test/unit/test-utils/render.js @@ -1,7 +1,8 @@ import { defu } from "defu" +import { renderSuspended } from "@nuxt/test-utils/runtime" + import { i18n } from "~~/test/unit/test-utils/i18n" -import { renderSuspended } from "~~/test/unit/test-utils/render-suspended" export const render = async (Component, options = {}) => { options = defu(options, { diff --git a/packages/js/eslint-plugin/src/configs/vue.ts b/packages/js/eslint-plugin/src/configs/vue.ts index dbd7277ac94..eeb072cac8b 100644 --- a/packages/js/eslint-plugin/src/configs/vue.ts +++ b/packages/js/eslint-plugin/src/configs/vue.ts @@ -36,6 +36,7 @@ export default tseslint.config( ], rules: { // Vue rules + "vue/no-required-prop-with-default": "error", /** * Custom rule to disallow raw `` tag usage. * Learn more about vue-eslint-parser's AST syntax: