diff --git a/.eslintignore b/.eslintignore index d95ecf4d139..5a1daedf208 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,7 @@ coverage +frontend/test/unit/test-utils/render-suspended.ts + frontend/test/tapes frontend/nuxt-template-overrides frontend/storybook-static 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 f149fb769da..06ce421bb53 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,7 +1,9 @@ -import { fireEvent, render } from "@testing-library/vue" +import { fireEvent } from "@testing-library/vue" import { createApp } 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" @@ -58,32 +60,32 @@ describe("AudioTrack", () => { } }) - it("should render the full audio track component even without duration", () => { + it("should render the full audio track component even without duration", async () => { options.props.layout = "full" - const { getByText } = render(VAudioTrack, options) + const { getByText } = await render(VAudioTrack, options) const creator = getByText(props.audio.creator) expect(creator).toBeInstanceOf(HTMLAnchorElement) }) - it("should show audio title as main page title in full layout", () => { + it("should show audio title as main page title in full layout", async () => { options.props.layout = "full" - const { getByText } = render(VAudioTrack, options) + const { getByText } = await render(VAudioTrack, options) // Title text appears multiple times in the track, so need to specify selector const element = getByText(props.audio.title, { selector: "H1" }) expect(element).toBeInTheDocument() }) - it("should show audio creator in a full layout with link", () => { + it("should show audio creator in a full layout with link", async () => { options.props.layout = "full" - const { getByText } = render(VAudioTrack, options) + const { getByText } = await render(VAudioTrack, options) const element = getByText(props.audio.creator) expect(element).toBeInstanceOf(HTMLAnchorElement) expect(element).toHaveAttribute("href", props.audio.creator_url) }) - it("should render the row audio track component even without duration", () => { + it("should render the row audio track component even without duration", async () => { options.props.layout = "row" - const { getByText } = render(VAudioTrack, options) + const { getByText } = await render(VAudioTrack, options) const creator = getByText("by " + props.audio.creator) expect(creator).toBeTruthy() }) @@ -115,7 +117,7 @@ describe("AudioTrack", () => { .spyOn(window.HTMLMediaElement.prototype, "play") .mockImplementation(() => Promise.reject(playError)) - const { getByRole, getByText } = render(VAudioTrack, options) + const { getByRole, getByText } = await render(VAudioTrack, options) await fireEvent.click(getByRole("button")) expect(playStub).toHaveBeenCalledTimes(1) @@ -137,7 +139,7 @@ describe("AudioTrack", () => { options.props.audio.isSensitive = true options.props.layout = "box" options.props.size = "large" - const { getByText } = render(VAudioTrack, options) + const { getByText } = await render(VAudioTrack, options) const h2 = getByText("This audio track may contain sensitive content.") expect(h2).toHaveClass("blur-text") }) @@ -145,7 +147,7 @@ describe("AudioTrack", () => { it("has blurred info in row layout when audio is sensitive", async () => { options.props.audio.isSensitive = true options.props.layout = "row" - const { getByText } = render(VAudioTrack, options) + const { getByText } = await render(VAudioTrack, options) const h2 = getByText("This audio track may contain sensitive content.") expect(h2).toHaveClass("blur-text") @@ -157,7 +159,7 @@ describe("AudioTrack", () => { it("is does not contain title or creator anywhere when the audio is sensitive", async () => { options.props.audio.isSensitive = true options.props.layout = "row" - const screen = render(VAudioTrack, options) + const screen = await render(VAudioTrack, options) let { title, creator } = options.props.audio let match = RegExp(`(${title}|${creator})`) expect(screen.queryAllByText(match)).toEqual([]) diff --git a/frontend/test/unit/specs/components/AudioTrack/v-box-layout.spec.js b/frontend/test/unit/specs/components/AudioTrack/v-box-layout.spec.js index c06e94d3ac7..2c09a67cc81 100644 --- a/frontend/test/unit/specs/components/AudioTrack/v-box-layout.spec.js +++ b/frontend/test/unit/specs/components/AudioTrack/v-box-layout.spec.js @@ -1,8 +1,7 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { getAudioObj } from "~~/test/unit/fixtures/audio" - -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VBoxLayout from "~/components/VAudioTrack/layouts/VBoxLayout.vue" @@ -14,15 +13,12 @@ describe("VBoxLayout", () => { } beforeEach(() => { - options = { - propsData: props, - global: { plugins: [i18n] }, - } + options = { props } }) - it("renders audio title, license and category in v-box-layout", () => { + it("renders audio title, license and category in v-box-layout", async () => { props.audio.category = "music" - render(VBoxLayout, options) + await render(VBoxLayout, options) const title = screen.getByText(props.audio.title) expect(title).toBeVisible() const license = screen.getByLabelText( @@ -33,9 +29,9 @@ describe("VBoxLayout", () => { expect(category).toBeVisible() }) - it("should not render category string if category is null", () => { + it("should not render category string if category is null", async () => { props.audio.category = null - render(VBoxLayout, options) + await render(VBoxLayout, options) const categoryLabel = screen.queryByText("Music") expect(categoryLabel).toBeNull() }) diff --git a/frontend/test/unit/specs/components/AudioTrack/v-full-layout.spec.js b/frontend/test/unit/specs/components/AudioTrack/v-full-layout.spec.js index 2e448664e3a..3f7dcacab87 100644 --- a/frontend/test/unit/specs/components/AudioTrack/v-full-layout.spec.js +++ b/frontend/test/unit/specs/components/AudioTrack/v-full-layout.spec.js @@ -1,18 +1,14 @@ -import { render } from "@testing-library/vue" - import { getAudioObj } from "~~/test/unit/fixtures/audio" - -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VFullLayout from "~/components/VAudioTrack/layouts/VFullLayout.vue" describe("VFullLayout", () => { - it("should render the weblink button with the foreign landing url", () => { + it("should render the weblink button with the foreign landing url", async () => { const audio = getAudioObj() - const { getByText } = render(VFullLayout, { - global: { plugins: [i18n] }, - propsData: { + const { getByText } = await render(VFullLayout, { + props: { audio, size: "s", status: "playing", diff --git a/frontend/test/unit/specs/components/AudioTrack/waveform.spec.js b/frontend/test/unit/specs/components/AudioTrack/waveform.spec.js index 451e8f6cf48..1edf1812b25 100644 --- a/frontend/test/unit/specs/components/AudioTrack/waveform.spec.js +++ b/frontend/test/unit/specs/components/AudioTrack/waveform.spec.js @@ -1,7 +1,5 @@ // vitest-environment jsdom -import { render } from "@testing-library/vue" - -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VWaveform from "~/components/VAudioTrack/VWaveform.vue" @@ -22,32 +20,27 @@ describe("VWaveform", () => { audioId: "test", } - options = { - propsData: props, - global: { - plugins: [i18n], - }, - } + options = { props } }) - it("should use given peaks when peaks array is provided", () => { + it("should use given peaks when peaks array is provided", async () => { const peaksCount = 5 props.peaks = Array.from({ length: peaksCount }, () => 0) - const { container } = render(VWaveform, options) + const { container } = await render(VWaveform, options) // There is also a yellow "played" rectangle expect(container.querySelectorAll("rect").length).toBe(peaksCount + 1) }) - it("should use random peaks when peaks not set", () => { + it("should use random peaks when peaks not set", async () => { const peaksCount = 100 - const { container } = render(VWaveform, options) + const { container } = await render(VWaveform, options) expect(container.querySelectorAll("rect")).toHaveLength(peaksCount + 1) }) - it("should use random peaks when peaks array is blank", () => { + it("should use random peaks when peaks array is blank", async () => { const peaksCount = 100 props.peaks = null - const { container } = render(VWaveform, options) + const { container } = await render(VWaveform, options) expect(container.querySelectorAll("rect")).toHaveLength(peaksCount + 1) }) }) 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 bdf396761b5..54ab7779945 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 @@ -1,6 +1,4 @@ -import { render } from "@testing-library/vue" - -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VCopyLicense from "~/components/VMediaInfo/VCopyLicense.vue" @@ -26,16 +24,11 @@ describe("VCopyLicense", () => { }, fullLicenseName: "LICENSE", } - options = { - propsData: props, - global: { - plugins: [i18n], - }, - } + options = { props } }) - it("should contain the correct contents", () => { - const { queryAllByText } = render(VCopyLicense, options) + it("should contain the correct contents", async () => { + const { queryAllByText } = await render(VCopyLicense, options) expect(queryAllByText(/Copy text/i)).toHaveLength(3) }) }) 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 599015a1f48..075f71d69f4 100644 --- a/frontend/test/unit/specs/components/InputField/input-field.spec.js +++ b/frontend/test/unit/specs/components/InputField/input-field.spec.js @@ -1,7 +1,9 @@ -import { screen, render } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { describe, expect, it } from "vitest" +import { render } from "~~/test/unit/test-utils/render" + import VInputField from "~/components/VInputField/VInputField.vue" const props = { @@ -12,7 +14,7 @@ const props = { describe("VInputField", () => { it('should render an `input` element with type="text"', async () => { - render(VInputField, { + await render(VInputField, { attrs: { placeholder: "Enter some text", }, @@ -25,7 +27,7 @@ describe("VInputField", () => { }) it("should allow changing the type", async () => { - render(VInputField, { + await render(VInputField, { attrs: { placeholder: "Enter some number", type: "number", @@ -39,7 +41,7 @@ describe("VInputField", () => { }) it("should set the ID on the `input` to allow attaching labels", async () => { - render(VInputField, { + await render(VInputField, { attrs: { placeholder: "Enter some text", }, @@ -52,7 +54,7 @@ describe("VInputField", () => { }) it("should render the label text connected to the input field if specified", async () => { - render(VInputField, { + await render(VInputField, { props: props, }) diff --git a/frontend/test/unit/specs/components/VHeader/SearchBar/search-bar.spec.js b/frontend/test/unit/specs/components/VHeader/SearchBar/search-bar.spec.js index 60a41eeca9c..9f89367b153 100644 --- a/frontend/test/unit/specs/components/VHeader/SearchBar/search-bar.spec.js +++ b/frontend/test/unit/specs/components/VHeader/SearchBar/search-bar.spec.js @@ -1,8 +1,8 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { beforeEach, describe, expect, it, vi } from "vitest" -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import { useMatchHomeRoute } from "~/composables/use-match-routes" @@ -22,7 +22,6 @@ describe("VSearchBar", () => { options = { props: { placeholder: defaultPlaceholder, size: "medium" }, stubs: { ClientOnly: true }, - global: { plugins: [i18n] }, } }) @@ -31,7 +30,7 @@ describe("VSearchBar", () => { async (size) => { useMatchHomeRoute.mockImplementation(() => false) options.props.size = size - render(VSearchBar, options) + await render(VSearchBar, options) const inputElement = screen.getByPlaceholderText(defaultPlaceholder) @@ -46,7 +45,7 @@ describe("VSearchBar", () => { async (size) => { useMatchHomeRoute.mockImplementation(() => false) options.props.size = size - render(VSearchBar, options) + await render(VSearchBar, options) const btnElement = screen.getByRole("button", { name: /search/i }) @@ -60,7 +59,7 @@ describe("VSearchBar", () => { it("should default to hero.search.placeholder", async () => { delete options.props.placeholder - render(VSearchBar, options) + await render(VSearchBar, options) expect( screen.queryByPlaceholderText(/Search for content/i) ).not.toBeNull() @@ -69,7 +68,7 @@ describe("VSearchBar", () => { it("should use the prop when provided", async () => { const placeholder = "This is a different placeholder from the default" options.props.placeholder = placeholder - render(VSearchBar, options) + await render(VSearchBar, options) expect(screen.queryByPlaceholderText(placeholder)).not.toBeNull() }) }) 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 0051fc1e53d..92490771780 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 @@ -1,14 +1,12 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { getAudioObj } from "~~/test/unit/fixtures/audio" - -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VMediaDetails from "~/components/VMediaInfo/VMediaDetails.vue" describe("VMediaDetails", () => { let options - let props const overrides = { audio_set: { @@ -21,21 +19,17 @@ describe("VMediaDetails", () => { } beforeEach(() => { - props = { - media: getAudioObj(overrides), - } options = { - propsData: props, + props: { media: getAudioObj(overrides) }, global: { stubs: ["VAudioThumbnail"], - plugins: [i18n], mocks: { route: { value: { name: "audio-id" } } }, }, } }) - it("renders the album title", () => { - render(VMediaDetails, options) + it("renders the album title", async () => { + await render(VMediaDetails, options) const album = screen.getByRole("link", { name: overrides.audio_set.title }) expect(album).toHaveAttribute( @@ -44,32 +38,26 @@ describe("VMediaDetails", () => { ) }) - it("hides the album title tag when it does not exists", () => { - options.propsData.media.audio_set = null - render(VMediaDetails, options) + it("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() }) - it("displays the main filetype when no alternative files are available", () => { - render(VMediaDetails, options) + it("displays the main filetype when no alternative files are available", async () => { + await render(VMediaDetails, options) expect(screen.queryByText("MP32")).toBeVisible() }) - it("displays multiple filetypes when they are available in alt_files", () => { - options.propsData.media.alt_files = [ - { filetype: "wav" }, - { filetype: "ogg" }, - ] - render(VMediaDetails, options) + it("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() }) - it("displays only distinct filetypes", () => { - options.propsData.media.alt_files = [ - { filetype: "ogg" }, - { filetype: "ogg" }, - ] - render(VMediaDetails, options) + it("displays only distinct filetypes", async () => { + options.props.media.alt_files = [{ filetype: "ogg" }, { filetype: "ogg" }] + await render(VMediaDetails, options) expect(screen.queryByText("MP32, OGG")).toBeVisible() }) }) diff --git a/frontend/test/unit/specs/components/VMediaTag/v-media-tag.spec.js b/frontend/test/unit/specs/components/VMediaTag/v-media-tag.spec.js index 6a3b4aabf11..e465406d1b2 100644 --- a/frontend/test/unit/specs/components/VMediaTag/v-media-tag.spec.js +++ b/frontend/test/unit/specs/components/VMediaTag/v-media-tag.spec.js @@ -1,39 +1,35 @@ import { RouterLinkStub } from "@vue/test-utils" import { render, screen } from "@testing-library/vue" -import { i18n } from "~~/test/unit/test-utils/i18n" - import VMediaTag from "~/components/VMediaTag/VMediaTag.vue" describe("VMediaTag", () => { - let props = null let options = null beforeEach(() => { - props = {} - options = { propsData: props, global: { plugins: [i18n] } } + options = { props: {}, global: { stubs: {} } } }) - it("should render an span tag by default", () => { - const { container } = render(VMediaTag, options) + it("should render an span tag by default", async () => { + const { container } = await render(VMediaTag, options) expect(container.firstChild.tagName).toEqual("SPAN") }) - it("should render the supplied tag", () => { - options.propsData = { - ...options.propsData, + it("should render the supplied tag", async () => { + options.props = { + ...options.props, tag: "a", href: "https://example.com/", } - const { container } = render(VMediaTag, options) + const { container } = await render(VMediaTag, options) expect(container.firstChild.tagName).toEqual("A") expect(container.firstChild.href).toEqual("https://example.com/") }) - it("should render the supplied Vue component", () => { - options.propsData = { - ...options.propsData, + it("should render the supplied Vue component", async () => { + options.props = { + ...options.props, tag: "RouterLink", to: "/", } @@ -41,17 +37,17 @@ describe("VMediaTag", () => { RouterLink: RouterLinkStub, } - const { container } = render(VMediaTag, options) + const { container } = await render(VMediaTag, options) expect(container.firstChild.tagName).toEqual("A") }) - it("renders slot content", () => { + it("renders slot content", async () => { const label = "I'm a label" options.slots = { default: () => `
Hello
`, } - render(VMediaTag, options) + await render(VMediaTag, options) expect(screen.queryByLabelText(label)).toBeDefined() }) }) 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 a569ac9de45..165d6fcbf06 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 @@ -1,7 +1,9 @@ -import { fireEvent, render, waitFor } from "@testing-library/vue" +import { fireEvent, waitFor } from "@testing-library/vue" 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" @@ -39,7 +41,7 @@ describe("VSafetyWall.vue", () => { }) it("emits reveal event when showMedia method is called", async () => { - const { getByText, emitted } = render(VSafetyWall, options) + const { getByText, emitted } = await render(VSafetyWall, options) const showButton = getByText("Show content") await fireEvent.click(showButton) @@ -52,7 +54,7 @@ describe("VSafetyWall.vue", () => { it("backToSearchPath gets the value from the store", async () => { const searchStore = useSearchStore() searchStore.setBackToSearchPath("/search") - const { findByText } = render(VSafetyWall, options) + const { findByText } = await render(VSafetyWall, options) const backToSearchButton = await findByText("Back to results") expect(backToSearchButton).toBeInTheDocument() diff --git a/frontend/test/unit/specs/components/scroll-button.spec.js b/frontend/test/unit/specs/components/scroll-button.spec.js index a357c2443e5..a7b1a6ddea5 100644 --- a/frontend/test/unit/specs/components/scroll-button.spec.js +++ b/frontend/test/unit/specs/components/scroll-button.spec.js @@ -1,12 +1,16 @@ -import { render, screen } from "@testing-library/vue" +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", () => { - const { container } = render(VScrollButton, { global: { plugins: [i18n] } }) + it("should render a scroll button", async () => { + const { container } = await render(VScrollButton, { + global: { plugins: [i18n] }, + }) 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-button.spec.js b/frontend/test/unit/specs/components/v-button.spec.js index 1d9b6f2069e..406dd1c9d91 100644 --- a/frontend/test/unit/specs/components/v-button.spec.js +++ b/frontend/test/unit/specs/components/v-button.spec.js @@ -1,9 +1,11 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { describe, expect, it } from "vitest" import { nextTick } from "vue" +import { render } from "~~/test/unit/test-utils/render" + import VButton from "~/components/VButton.vue" /** @@ -15,7 +17,7 @@ import VButton from "~/components/VButton.vue" */ describe("VButton", () => { it('should render a `button` by default with type="button" and no tabindex', async () => { - render(VButton, { + await render(VButton, { props: { variant: "filled-white", size: "medium" }, slots: { default: () => "Code is Poetry" }, }) @@ -28,7 +30,7 @@ describe("VButton", () => { }) it("should allow passing an explicit type", async () => { - render(VButton, { + await render(VButton, { props: { type: "submit", variant: "filled-white", size: "medium" }, slots: { default: () => "Code is Poetry" }, }) @@ -39,7 +41,7 @@ describe("VButton", () => { }) it("should render an anchor with no type attribute", async () => { - render(VButton, { + await render(VButton, { attrs: { href: "http://localhost" }, props: { as: "VLink", variant: "filled-white", size: "medium" }, slots: { default: () => "Code is Poetry" }, @@ -53,7 +55,7 @@ describe("VButton", () => { }) it("should render the disabled attribute on a button when the element is explicitly unfocusableWhenDisabled and is disabled", async () => { - render(VButton, { + await render(VButton, { props: { disabled: true, focusableWhenDisabled: false, @@ -69,7 +71,7 @@ describe("VButton", () => { }) it("should not render the disabled attribute if the element is focusableWhenDisabled", async () => { - render(VButton, { + await render(VButton, { props: { disabled: true, focusableWhenDisabled: true, @@ -86,7 +88,7 @@ describe("VButton", () => { }) it("should not render the disabled attribute on elements that do not support it", async () => { - render(VButton, { + await render(VButton, { props: { as: "VLink", disabled: true, 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 a418b3ed966..72a57241848 100644 --- a/frontend/test/unit/specs/components/v-content-link.spec.js +++ b/frontend/test/unit/specs/components/v-content-link.spec.js @@ -1,9 +1,11 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { beforeEach, describe, expect, it } from "vitest" 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" @@ -37,7 +39,7 @@ describe("VContentLink", () => { }) it("is enabled when there are results", async () => { - render(VContentLink, options) + await render(VContentLink, options) const btn = screen.getByRole("link") expect(btn).toHaveAttribute("href") diff --git a/frontend/test/unit/specs/components/v-copy-button.spec.js b/frontend/test/unit/specs/components/v-copy-button.spec.js index cbbc3fd82f7..c98f8ebfc25 100644 --- a/frontend/test/unit/specs/components/v-copy-button.spec.js +++ b/frontend/test/unit/specs/components/v-copy-button.spec.js @@ -3,16 +3,13 @@ * Actual copying is being tested by the e2e tests: * test/playwright/e2e/attribution.spec.ts */ -import { render } from "@testing-library/vue" - -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VCopyButton from "~/components/VCopyButton.vue" describe("VCopyButton", () => { - it("should render correct contents", () => { - const { getByRole } = render(VCopyButton, { - global: { plugins: [i18n] }, + it("should render correct contents", async () => { + const { getByRole } = await render(VCopyButton, { props: { el: "#foo", id: "foo", 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 c822f31ccb4..41478ed7f9a 100644 --- a/frontend/test/unit/specs/components/v-image-cell.spec.js +++ b/frontend/test/unit/specs/components/v-image-cell.spec.js @@ -1,9 +1,7 @@ -import { render } from "@testing-library/vue" - 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" @@ -38,14 +36,14 @@ describe("VImageCell", () => { it("is blurred when the image is sensitive", async () => { options.props.image.isSensitive = true - const { getByAltText } = render(VImageCell, options) + const { getByAltText } = await render(VImageCell, options) const img = getByAltText("This image may contain sensitive content.") expect(img).toHaveClass("blur-image") }) it("is does not contain title anywhere when the image is sensitive", async () => { options.props.image.isSensitive = true - const screen = render(VImageCell, options) + const screen = await render(VImageCell, options) let match = RegExp(image.title) expect(screen.queryAllByText(match)).toEqual([]) expect(screen.queryAllByTitle(match)).toEqual([]) diff --git a/frontend/test/unit/specs/components/v-item-group.spec.js b/frontend/test/unit/specs/components/v-item-group.spec.js index 2f0a450fd76..8ba0572724e 100644 --- a/frontend/test/unit/specs/components/v-item-group.spec.js +++ b/frontend/test/unit/specs/components/v-item-group.spec.js @@ -1,8 +1,8 @@ import { createApp, ref } from "vue" -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { default as userEvent } from "@testing-library/user-event" -import { describe, expect, it } from "vitest" +import { render } from "~~/test/unit/test-utils/render" import VItemGroup from "~/components/VItemGroup/VItemGroup.vue" import VItem from "~/components/VItemGroup/VItem.vue" @@ -50,7 +50,7 @@ const TestWrapper = createApp({}).component("TestWrapper", { describe("VItemGroup", () => { it("should render buttons with the appropriate roles", async () => { - render(TestWrapper) + await render(TestWrapper) expect(screen.queryByRole("menu")).toBeVisible() const items = screen.queryAllByRole("menuitemcheckbox") expect(items).toHaveLength(4) @@ -58,7 +58,7 @@ describe("VItemGroup", () => { }) it("should render functional buttons", async () => { - const { container } = render(TestWrapper) + const { container } = await render(TestWrapper) const [, secondItem] = screen.queryAllByRole("menuitemcheckbox") expect( container.querySelector('[aria-pressed="true"][aria-checked="true"]') @@ -70,7 +70,7 @@ describe("VItemGroup", () => { }) it("should render a radio group", async () => { - const { container } = render(TestWrapper, { + const { container } = await render(TestWrapper, { attrs: { type: "radiogroup" }, }) expect(screen.queryByRole("radiogroup")).not.toBeNull() @@ -86,7 +86,7 @@ describe("VItemGroup", () => { describe("navigation", () => { it("should render the first item tabbable when there is no default selection and none are selected", async () => { - const { container } = render(TestWrapper, { + const { container } = await render(TestWrapper, { props: { hasDefaultSelection: false }, attrs: { type: "radiogroup" }, }) @@ -97,7 +97,7 @@ describe("VItemGroup", () => { }) it("should render all items tabbable when in a menu", async () => { - const { container } = render(TestWrapper, { + const { container } = await render(TestWrapper, { attrs: { type: "menu" }, }) const tabbableElements = container.querySelectorAll('[tabindex="0"]') @@ -106,7 +106,7 @@ describe("VItemGroup", () => { }) it("should render only the selected item as tabbable", async () => { - const { container } = render(TestWrapper, { + const { container } = await render(TestWrapper, { attrs: { type: "radiogroup" }, props: { hasDefaultSelection: false }, }) @@ -121,7 +121,7 @@ describe("VItemGroup", () => { }) it("should render the currently focused item as tabbable even when there is a selection", async () => { - const { container } = render(TestWrapper, { + const { container } = await render(TestWrapper, { attrs: { type: "radiogroup" }, }) const [, secondItem] = screen.queryAllByRole("radio") @@ -136,7 +136,7 @@ describe("VItemGroup", () => { it.each(["ArrowUp", "ArrowLeft"])( "should focus to the previous item on %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, secondItem] = screen.queryAllByRole("radio") await doFocus(secondItem) @@ -148,7 +148,7 @@ describe("VItemGroup", () => { it.each(["ArrowUp", "ArrowLeft"])( "should go to the last item when on the first item and pressing %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, , , lastItem] = screen.queryAllByRole("radio") await doFocus(firstItem) await userEvent.keyboard(`{${key}}`) @@ -159,7 +159,7 @@ describe("VItemGroup", () => { it.each(["ArrowDown", "ArrowRight"])( "should focus to the next item on %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, secondItem] = screen.queryAllByRole("radio") await doFocus(firstItem) @@ -171,7 +171,7 @@ describe("VItemGroup", () => { it.each(["ArrowDown", "ArrowRight"])( "should go to the first item when on the last item and pressing %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, , , lastItem] = screen.queryAllByRole("radio") await doFocus(lastItem) @@ -185,7 +185,7 @@ describe("VItemGroup", () => { it.each(["ArrowUp", "ArrowLeft"])( "should focus to the previous item on %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, secondItem] = screen.queryAllByRole("radio") await doFocus(secondItem) @@ -197,7 +197,7 @@ describe("VItemGroup", () => { it.each(["ArrowUp", "ArrowLeft"])( "should go to the last item when on the first item and pressing %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, , , lastItem] = screen.queryAllByRole("radio") await doFocus(firstItem) await userEvent.keyboard(`{${key}}`) @@ -208,7 +208,7 @@ describe("VItemGroup", () => { it.each(["ArrowDown", "ArrowRight"])( "should focus to the next item on %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, secondItem] = screen.queryAllByRole("radio") await doFocus(firstItem) @@ -220,7 +220,7 @@ describe("VItemGroup", () => { it.each(["ArrowDown", "ArrowRight"])( "should go to the first item when on the last item and pressing %s", async (key) => { - render(TestWrapper, { attrs: { type: "radiogroup" } }) + await render(TestWrapper, { attrs: { type: "radiogroup" } }) const [firstItem, , , lastItem] = screen.queryAllByRole("radio") await doFocus(lastItem) @@ -235,7 +235,7 @@ describe("VItemGroup", () => { it.each(["ArrowUp"])( "should focus to the previous item on %s", async (key) => { - render(TestWrapper, { + await render(TestWrapper, { attrs: { type: "radiogroup", direction: "horizontal" }, }) const [firstItem, secondItem] = screen.queryAllByRole("radio") @@ -250,7 +250,7 @@ describe("VItemGroup", () => { it.each(["ArrowUp"])( "should go to the last item when on the first item and pressing %s", async (key) => { - render(TestWrapper, { + await render(TestWrapper, { attrs: { type: "radiogroup", direction: "horizontal" }, }) const [firstItem, , , lastItem] = screen.queryAllByRole("radio") @@ -264,7 +264,7 @@ describe("VItemGroup", () => { it.each(["ArrowDown"])( "should focus to the next item on %s", async (key) => { - render(TestWrapper, { + await render(TestWrapper, { attrs: { type: "radiogroup", direction: "horizontal" }, }) const [firstItem, secondItem] = screen.queryAllByRole("radio") @@ -279,7 +279,7 @@ describe("VItemGroup", () => { it.each(["ArrowDown"])( "should go to the first item when on the last item and pressing %s", async (key) => { - render(TestWrapper, { + await render(TestWrapper, { attrs: { type: "radiogroup", direction: "horizontal" }, }) const [firstItem, , , lastItem] = screen.queryAllByRole("radio") diff --git a/frontend/test/unit/specs/components/v-license.spec.js b/frontend/test/unit/specs/components/v-license.spec.js index 27f448fee18..349fe4e8dce 100644 --- a/frontend/test/unit/specs/components/v-license.spec.js +++ b/frontend/test/unit/specs/components/v-license.spec.js @@ -1,8 +1,8 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { describe, expect, it } from "vitest" -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VLicense from "~/components/VLicense/VLicense.vue" @@ -11,11 +11,11 @@ describe("VLicense", () => { props: { license: "by", }, - global: { plugins: [i18n], stubs: { VIcon: true } }, + global: { stubs: { VIcon: true } }, } it("should render the license name and icons", async () => { - const { container } = render(VLicense, options) + const { container } = await render(VLicense, options) const licenseName = screen.getByLabelText("Attribution") expect(licenseName).toBeInTheDocument() const licenseIcons = container.querySelectorAll("v-icon-stub") @@ -24,7 +24,7 @@ describe("VLicense", () => { it("should render only the license icons", async () => { options.props.hideName = true - const { container } = render(VLicense, options) + const { container } = await render(VLicense, options) const licenseName = screen.queryByText("CC BY") expect(licenseName).not.toBeVisible() const licenseIcons = container.querySelectorAll("v-icon-stub") @@ -33,7 +33,7 @@ describe("VLicense", () => { it("should have background filled with black text", async () => { options.props.bgFilled = true - const { container } = render(VLicense, options) + const { container } = await render(VLicense, options) const licenseIcons = container.querySelectorAll("v-icon-stub") expect(licenseIcons).toHaveLength(2) licenseIcons.forEach((icon) => { diff --git a/frontend/test/unit/specs/components/v-link.spec.js b/frontend/test/unit/specs/components/v-link.spec.js index 55486dd2cd7..1337bcda4b9 100644 --- a/frontend/test/unit/specs/components/v-link.spec.js +++ b/frontend/test/unit/specs/components/v-link.spec.js @@ -30,7 +30,7 @@ describe("VLink", () => { async ({ href, target, rel }) => { options.props = { href } options.slots = { default: () => "Code is Poetry" } - render(VLink, options) + await render(VLink, options) const link = screen.getByRole("link") const expectedHref = href.startsWith("/") ? `http://localhost:3000${href}` @@ -62,7 +62,7 @@ describe("VLink", () => { `, })._context.components.VLinkWrapper const WrapperComponent = createVLinkWrapper(href) - render(WrapperComponent, options) + await render(WrapperComponent, options) const linkBefore = screen.getByRole("link") expect(linkBefore.textContent).toBe("Link Text") diff --git a/frontend/test/unit/specs/components/v-logo-loader.spec.js b/frontend/test/unit/specs/components/v-logo-loader.spec.js index 4d9a768cd36..8753d404881 100644 --- a/frontend/test/unit/specs/components/v-logo-loader.spec.js +++ b/frontend/test/unit/specs/components/v-logo-loader.spec.js @@ -19,7 +19,7 @@ vi.mock("~/composables/use-reduced-motion", () => ({ describe("VLogoLoader", () => { it("should render the logo", async () => { - render(VLogoLoader) + await render(VLogoLoader) const element = screen.getByTestId("logo-loader") expect(element).toBeInTheDocument() }) @@ -28,7 +28,7 @@ describe("VLogoLoader", () => { it("should render differently when the user prefers reduced motion", async () => { useReducedMotion.mockImplementation(() => true) - render(VLogoLoader, { + await render(VLogoLoader, { props: { status: "loading" }, }) const element = screen.getByTestId("logo-loader") @@ -37,7 +37,7 @@ describe("VLogoLoader", () => { it("should show the default loading style when no motion preference is set", async () => { useReducedMotion.mockImplementation(() => false) - render(VLogoLoader, { + await render(VLogoLoader, { props: { status: "loading" }, }) const element = screen.getByTestId("logo-loader") diff --git a/frontend/test/unit/specs/components/v-modal.spec.js b/frontend/test/unit/specs/components/v-modal.spec.js index 72fe20924cf..d6631ea95a2 100644 --- a/frontend/test/unit/specs/components/v-modal.spec.js +++ b/frontend/test/unit/specs/components/v-modal.spec.js @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { afterAll, beforeAll, @@ -10,10 +9,10 @@ import { } from "vitest" import { ref, computed, createApp } from "vue" -import { screen, render } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { default as userEvent } from "@testing-library/user-event" -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import VModal from "~/components/VModal/VModal.vue" import VButton from "~/components/VButton.vue" @@ -75,7 +74,6 @@ describe("VModal", () => { beforeEach(() => { options = { props: { useCustomInitialFocus: false }, - global: { plugins: [i18n] }, } vi.resetAllMocks() }) diff --git a/frontend/test/unit/specs/components/v-popover.spec.js b/frontend/test/unit/specs/components/v-popover.spec.js index 15fa88db3ba..917f851e348 100644 --- a/frontend/test/unit/specs/components/v-popover.spec.js +++ b/frontend/test/unit/specs/components/v-popover.spec.js @@ -1,9 +1,11 @@ import { createApp, nextTick } from "vue" -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { default as userEvent } from "@testing-library/user-event" import { describe, expect, it } from "vitest" +import { render } from "~~/test/unit/test-utils/render" + import VButton from "~/components/VButton.vue" import VPopover from "~/components/VPopover/VPopover.vue" @@ -25,7 +27,7 @@ const TestWrapper = createApp({}).component("TestWrapper", {
External area
Code is Poetry
@@ -33,8 +35,8 @@ const TestWrapper = createApp({}).component("TestWrapper", { `, })._context.components.TestWrapper -const getPopover = () => screen.getByText(/code is poetry/i) -const queryPopover = () => screen.queryByText(/code is poetry/i) +const getPopover = () => screen.getByRole("dialog", { hidden: true }) +const queryPopover = () => screen.queryByRole("dialog", { hidden: true }) const getTrigger = () => screen.getByRole("button", {}) const getExternalArea = () => screen.getByText(/external area/i) const clickOutside = () => userEvent.click(getExternalArea()) @@ -46,9 +48,9 @@ const doOpen = async (trigger = getTrigger()) => { describe("VPopover", () => { it("should open the popover when the trigger is clicked", async () => { - render(TestWrapper) + await render(TestWrapper) - expect(queryPopover()).not.toBeInTheDocument() + expect(queryPopover()).not.toBeVisible() await doOpen() expect(getPopover()).toBeVisible() @@ -56,7 +58,7 @@ describe("VPopover", () => { describe("accessibility", () => { it("should render a11y props for the trigger", async () => { - render(TestWrapper) + await render(TestWrapper) const trigger = getTrigger() @@ -73,17 +75,17 @@ describe("VPopover", () => { describe("autoFocusOnShow", () => { it("should focus the popover by default when opening if there is no tabbable content in the popover and warn", async () => { - render(TestWrapper) + await render(TestWrapper) await doOpen() expect(getPopover()).toBeVisible() - expect(getPopover().parentElement).toHaveFocus() + expect(getPopover()).toHaveFocus() // TODO: Add a spy or a mock for this // expect(warn).toHaveBeenCalledWith(noFocusableElementWarning) }) it("should neither focus no warn when the prop is false", async () => { - render(TestWrapper, { + await render(TestWrapper, { props: { popoverProps: { autoFocusOnShow: false } }, }) getTrigger().focus() @@ -97,7 +99,7 @@ describe("VPopover", () => { describe("autoFocusOnHide", () => { it("should return focus to the trigger", async () => { - render(TestWrapper, { + await render(TestWrapper, { props: { popoverProps: { trapFocus: false } }, }) await doOpen() @@ -108,7 +110,7 @@ describe("VPopover", () => { }) it("should not return focus to the trigger when false", async () => { - render(TestWrapper, { + await render(TestWrapper, { props: { popoverProps: { trapFocus: false, autoFocusOnHide: false } }, }) await doOpen() @@ -121,7 +123,7 @@ describe("VPopover", () => { describe("hideOnClickOutside", () => { it("should hide the popover if a click happens outside the popover by default", async () => { - render(TestWrapper) + await render(TestWrapper) await doOpen() expect(getPopover()).toBeVisible() @@ -131,7 +133,7 @@ describe("VPopover", () => { }) it("should not hide the popover if a click happens outside the popover when false", async () => { - render(TestWrapper, { + await render(TestWrapper, { props: { popoverProps: { hideOnClickOutside: false } }, }) @@ -145,7 +147,7 @@ describe("VPopover", () => { describe("hideOnEsc", () => { it("should hide the popover if escape is sent in the popover by default", async () => { - render(TestWrapper) + await render(TestWrapper) await doOpen() expect(getPopover()).toBeVisible() await userEvent.keyboard("{escape}") @@ -153,7 +155,7 @@ describe("VPopover", () => { }) it("should not hide if the escape is sent in the popover when false", async () => { - render(TestWrapper, { + await render(TestWrapper, { props: { popoverProps: { hideOnEsc: false } }, }) await doOpen() diff --git a/frontend/test/unit/specs/components/v-search-results-title.spec.js b/frontend/test/unit/specs/components/v-search-results-title.spec.js index 3e2e67d15b0..60682fb49b4 100644 --- a/frontend/test/unit/specs/components/v-search-results-title.spec.js +++ b/frontend/test/unit/specs/components/v-search-results-title.spec.js @@ -1,7 +1,7 @@ -import { render } from "@testing-library/vue" - import { describe, expect, it } from "vitest" +import { render } from "~~/test/unit/test-utils/render" + import VSearchResultsTitle from "~/components/VSearchResultsTitle.vue" describe("VSearchResultsTitle", () => { @@ -15,7 +15,7 @@ describe("VSearchResultsTitle", () => { } it("should render an h1 tag containing the correct text", async () => { - const { getByRole } = render(VSearchResultsTitle, options) + const { getByRole } = await render(VSearchResultsTitle, options) const title = getByRole("heading", { level: 1, name: "zack" }) expect(title).toBeInTheDocument() }) diff --git a/frontend/test/unit/specs/components/v-sources-table.spec.js b/frontend/test/unit/specs/components/v-sources-table.spec.js index 89b41b762cb..c4dfb662ff1 100644 --- a/frontend/test/unit/specs/components/v-sources-table.spec.js +++ b/frontend/test/unit/specs/components/v-sources-table.spec.js @@ -1,9 +1,9 @@ -import { render, screen } from "@testing-library/vue" +import { screen } from "@testing-library/vue" import { default as userEvent } from "@testing-library/user-event" import { beforeEach, describe, expect, it } from "vitest" -import { i18n } from "~~/test/unit/test-utils/i18n" +import { render } from "~~/test/unit/test-utils/render" import { useProviderStore } from "~/stores/provider" @@ -50,14 +50,13 @@ describe("VSourcesTable", () => { options = { props: { media: "image" }, global: { - plugins: [i18n], stubs: ["TableSortIcon"], }, } }) it('should be sorted by display_name ("Source") by default', async () => { - render(VSourcesTable, options) + await render(VSourcesTable, options) const table = getTableData(screen.getAllByRole("row")) const expectedTable = [ @@ -69,7 +68,7 @@ describe("VSourcesTable", () => { }) it('should be sorted by clean url when click on "Domain" header', async () => { - render(VSourcesTable, options) + await render(VSourcesTable, options) const domainCell = screen.getByRole("columnheader", { name: /domain/i, }) @@ -85,7 +84,7 @@ describe("VSourcesTable", () => { // https://github.com/wordpress/openverse/issues/411 it.skip('should be sorted by media_count when click on "Total items" header', async () => { - render(VSourcesTable, options) + await render(VSourcesTable, options) const domainCell = screen.getByRole("columnheader", { name: /total items/i, }) diff --git a/frontend/test/unit/specs/composables/default-ref.spec.ts b/frontend/test/unit/specs/composables/default-ref.spec.ts index 2b0692dc044..120a176dc5c 100644 --- a/frontend/test/unit/specs/composables/default-ref.spec.ts +++ b/frontend/test/unit/specs/composables/default-ref.spec.ts @@ -3,13 +3,13 @@ import { describe, expect, it } from "vitest" import { defaultRef } from "~/composables/default-ref" describe("defaultRef", () => { - it("should use the default value", () => { + it("should use the default value", async () => { const getDefault = () => 100 const value = defaultRef(getDefault) expect(value.value).toBe(100) }) - it("should use the set value", () => { + it("should use the set value", async () => { const value = defaultRef(() => "a") value.value = "b" expect(value.value).toBe("b") diff --git a/frontend/test/unit/specs/composables/use-image-cell-size.spec.js b/frontend/test/unit/specs/composables/use-image-cell-size.spec.js index 11a9abfd706..f7f9b652d16 100644 --- a/frontend/test/unit/specs/composables/use-image-cell-size.spec.js +++ b/frontend/test/unit/specs/composables/use-image-cell-size.spec.js @@ -3,7 +3,7 @@ import { ref } from "vue" import { useImageCellSize } from "~/composables/use-image-cell-size" describe("useImageCellSize", () => { - it("Should return correct values for square image", () => { + it("Should return correct values for square image", async () => { const { imgHeight, imgWidth, isPanorama, styles } = useImageCellSize({ imageSize: {}, isSquare: ref(true), @@ -15,7 +15,7 @@ describe("useImageCellSize", () => { expect(styles.value).toEqual({}) }) - it("Should return correct values for intrinsic panorama image", () => { + it("Should return correct values for intrinsic panorama image", async () => { const HEIGHT = 25 const WIDTH = 300 const { imgHeight, imgWidth, isPanorama, styles } = useImageCellSize({ @@ -33,7 +33,7 @@ describe("useImageCellSize", () => { }) }) - it("Should return correct values for intrinsic tall image", () => { + it("Should return correct values for intrinsic tall image", async () => { const HEIGHT = 300 const WIDTH = 25 const { imgHeight, imgWidth, isPanorama, styles } = useImageCellSize({ @@ -51,7 +51,7 @@ describe("useImageCellSize", () => { }) }) - it("Should return correct values for intrinsic square image", () => { + it("Should return correct values for intrinsic square image", async () => { const HEIGHT = 300 const WIDTH = 300 const { imgHeight, imgWidth, isPanorama, styles } = useImageCellSize({ diff --git a/frontend/test/unit/specs/composables/use-search-type.spec.js b/frontend/test/unit/specs/composables/use-search-type.spec.js index 3cc74f0a635..af358518c3b 100644 --- a/frontend/test/unit/specs/composables/use-search-type.spec.js +++ b/frontend/test/unit/specs/composables/use-search-type.spec.js @@ -11,7 +11,7 @@ describe("useSearchType", () => { setActivePinia(createPinia()) }) - it("should have correct initial values", () => { + it("should have correct initial values", async () => { const { activeType, types: searchTypes, @@ -38,7 +38,7 @@ describe("useSearchType", () => { expect(additionalTypes.value).toEqual([]) }) - it("should return correct props for active search type when type is not passed", () => { + it("should return correct props for active search type when type is not passed", async () => { const { getSearchTypeProps } = useSearchType() const { icon, label } = getSearchTypeProps() @@ -46,7 +46,7 @@ describe("useSearchType", () => { expect(label).toBe("All content") }) - it("should return correct props when type is passed", () => { + it("should return correct props when type is passed", async () => { const { getSearchTypeProps } = useSearchType() const { icon, label } = getSearchTypeProps(AUDIO) diff --git a/frontend/test/unit/specs/composables/use-sensitive-media.spec.ts b/frontend/test/unit/specs/composables/use-sensitive-media.spec.ts index e26a415645b..9ac28af3878 100644 --- a/frontend/test/unit/specs/composables/use-sensitive-media.spec.ts +++ b/frontend/test/unit/specs/composables/use-sensitive-media.spec.ts @@ -49,24 +49,24 @@ describe("useSensitiveMedia composable", () => { })) }) - it("should return non-sensitive when media is null", () => { + it("should return non-sensitive when media is null", async () => { const { visibility } = useSensitiveMedia(null) expect(visibility.value).toBe("non-sensitive") }) - it("should return non-sensitive when media is not sensitive", () => { + it("should return non-sensitive when media is not sensitive", async () => { const { visibility } = useSensitiveMedia(mockMedia) expect(visibility.value).toBe("non-sensitive") }) - it("should return sensitive-hidden when media is sensitive and shouldBlurSensitive is true", () => { + it("should return sensitive-hidden when media is sensitive and shouldBlurSensitive is true", async () => { mockMedia.isSensitive = true const { visibility } = useSensitiveMedia(mockMedia) expect(visibility.value).toBe("sensitive-hidden") }) - it("should return sensitive-shown when media is sensitive and shouldBlurSensitive is false", () => { + it("should return sensitive-shown when media is sensitive and shouldBlurSensitive is false", async () => { mockMedia.isSensitive = true mockUseUiStore.shouldBlurSensitive = false @@ -74,38 +74,7 @@ describe("useSensitiveMedia composable", () => { expect(visibility.value).toBe("sensitive-shown") }) - // TODO: Move the test to e2e - // https://github.com/wordpress/openverse/issues/411 - it.skip("should reveal sensitive media", () => { - mockMedia.isSensitive = true - - const { reveal, visibility } = useSensitiveMedia(mockMedia) - reveal() - - expect(visibility.value).toBe("sensitive-shown") - expect(sendCustomEventMock).toHaveBeenCalledWith( - "UNBLUR_SENSITIVE_RESULT", - { id: "mock-id", sensitivities: "" } - ) - }) - - // TODO: Move the test to e2e - // https://github.com/wordpress/openverse/issues/411 - it.skip("should hide sensitive media", () => { - mockMedia.isSensitive = true - - const { reveal, hide, visibility } = useSensitiveMedia(mockMedia) - reveal() - hide() - - expect(visibility.value).toBe("sensitive-hidden") - expect(sendCustomEventMock).toHaveBeenCalledWith( - "REBLUR_SENSITIVE_RESULT", - { id: "mock-id", sensitivities: "" } - ) - }) - - it("should correctly report if a media is hidden", () => { + it("should correctly report if a media is hidden", async () => { mockMedia.isSensitive = true const { reveal, hide, isHidden } = useSensitiveMedia(mockMedia) @@ -115,7 +84,7 @@ describe("useSensitiveMedia composable", () => { expect(isHidden.value).toBe(true) }) - it("should correctly report if a media can be hidden", () => { + it("should correctly report if a media can be hidden", async () => { mockMedia.isSensitive = true mockUseUiStore.shouldBlurSensitive = false diff --git a/frontend/test/unit/specs/utils/attribution-html.spec.ts b/frontend/test/unit/specs/utils/attribution-html.spec.ts index 2bdaf7dd846..8a4ac752f2a 100644 --- a/frontend/test/unit/specs/utils/attribution-html.spec.ts +++ b/frontend/test/unit/specs/utils/attribution-html.spec.ts @@ -16,7 +16,7 @@ const mediaItem: AttributableMedia = { } describe("getAttribution", () => { - it("returns attribution for media with i18n", () => { + it("returns attribution for media with i18n", async () => { const attributionText = '"Title" by Creator is marked with Public Domain Mark 1.0 .' document.body.innerHTML = getAttribution(mediaItem, i18n) @@ -25,7 +25,7 @@ describe("getAttribution", () => { }) // TODO: fix fakeT function - it("returns attribution for media without i18n", () => { + it("returns attribution for media without i18n", async () => { // const attributionText = '"Title" by Creator is marked with PDM 1.0 .' console.log(getAttribution(mediaItem, null)) document.body.innerHTML = getAttribution(mediaItem, null) @@ -33,7 +33,7 @@ describe("getAttribution", () => { expect(attributionP.textContent?.trim()).toBe("") }) - it("uses generic title if not known", () => { + it("uses generic title if not known", async () => { const mediaItemNoTitle = { ...mediaItem, originalTitle: "" } const attrText = getAttribution(mediaItemNoTitle, i18n, { isPlaintext: true, @@ -43,7 +43,7 @@ describe("getAttribution", () => { expect(attrText).toContain(expectation) }) - it("omits creator if not known", () => { + it("omits creator if not known", async () => { const mediaItemNoCreator = { ...mediaItem, creator: undefined } const attrText = getAttribution(mediaItemNoCreator, i18n, { isPlaintext: true, @@ -52,7 +52,7 @@ describe("getAttribution", () => { expect(attrText).toContain(expectation) }) - it("escapes embedded HTML", () => { + it("escapes embedded HTML", async () => { const mediaItemWithHtml = { ...mediaItem, originalTitle: '', @@ -64,7 +64,7 @@ describe("getAttribution", () => { expect(attrText).not.toContain(mediaItemWithHtml.originalTitle) }) - it("does not use anchors in plain-text mode", () => { + it("does not use anchors in plain-text mode", async () => { document.body.innerHTML = getAttribution(mediaItem, i18n) expect(document.getElementsByTagName("a")).not.toHaveLength(0) document.body.innerHTML = getAttribution(mediaItem, i18n, { @@ -73,14 +73,14 @@ describe("getAttribution", () => { expect(document.getElementsByTagName("a")).toHaveLength(0) }) - it("renders the correct text in plain-text mode", () => { + it("renders the correct text in plain-text mode", async () => { const attrText = getAttribution(mediaItem, i18n, { isPlaintext: true }) const expectation = '"Title" by Creator is marked with Public Domain Mark 1.0. To view the terms, visit https://license/url?ref=openverse.' expect(attrText).toEqual(expectation) }) - it("skips the link if URL is missing", () => { + it("skips the link if URL is missing", async () => { const mediaItemNoLicenseUrl = { ...mediaItem, license_url: undefined } const attrText = getAttribution(mediaItemNoLicenseUrl, i18n, { isPlaintext: true, diff --git a/frontend/test/unit/specs/utils/decode-data.spec.js b/frontend/test/unit/specs/utils/decode-data.spec.js index 4c1489691de..e0457204197 100644 --- a/frontend/test/unit/specs/utils/decode-data.spec.js +++ b/frontend/test/unit/specs/utils/decode-data.spec.js @@ -3,43 +3,43 @@ import { describe, expect, it } from "vitest" import { decodeData } from "~/utils/decode-data" describe("decodeData", () => { - it("returns empty string for empty string", () => { + it("returns empty string for empty string", async () => { const data = "" expect(decodeData(data)).toBe("") }) - it("returns empty string for undefined data", () => { + it("returns empty string for undefined data", async () => { const data = undefined expect(decodeData(data)).toBe("") }) - it("returns decoded ASCII hexacode strings", () => { + it("returns decoded ASCII hexacode strings", async () => { const data = "s\\xe9" expect(decodeData(data)).toBe("sé") }) - it("returns decoded unicode strings", () => { + it("returns decoded unicode strings", async () => { const data = "s\\u1234" expect(decodeData(data)).toBe("s\u1234") }) - it("returns decoded strings consisting of both unicode and ASCII hexacode characters", () => { + it("returns decoded strings consisting of both unicode and ASCII hexacode characters", async () => { const data = "s\\u1234\xe9" expect(decodeData(data)).toBe("s\u1234\xe9") }) - it("returns decoded string when string does not contain backslash", () => { + it("returns decoded string when string does not contain backslash", async () => { const data = "musu00e9e" expect(decodeData(data)).toBe("musée") }) - it("shouldn't throw exception", () => { + it("shouldn't throw exception", async () => { const data = "Classic Twill - SlipcoverFabrics.com 100% Cotton" expect(decodeData(data)).toBe(data) diff --git a/frontend/test/unit/specs/utils/decode-image-data.spec.js b/frontend/test/unit/specs/utils/decode-image-data.spec.js index 5bdfe6e203c..06278eb2844 100644 --- a/frontend/test/unit/specs/utils/decode-image-data.spec.js +++ b/frontend/test/unit/specs/utils/decode-image-data.spec.js @@ -28,7 +28,7 @@ describe("decodeImageData", () => { setActivePinia(createPinia()) }) - it("decodes symbols correctly", () => { + it("decodes symbols correctly", async () => { const data = { ...requiredFields, creator: "S\\xe3", @@ -48,7 +48,7 @@ describe("decodeImageData", () => { expect(decodeMediaData(data, IMAGE)).toEqual(expected) }) - it("strips the extension if the same as media filetype", () => { + it("strips the extension if the same as media filetype", async () => { const data = { ...requiredFields, creator: "Creator", @@ -68,7 +68,7 @@ describe("decodeImageData", () => { expect(decodeMediaData(data, IMAGE)).toEqual(expected) }) - it("strips the extension if the same as url extension", () => { + it("strips the extension if the same as url extension", async () => { const data = { ...requiredFields, url: "https://example.com/image.jpg", @@ -87,7 +87,7 @@ describe("decodeImageData", () => { expect(decodeMediaData(data, IMAGE)).toEqual(expected) }) - it("does not strip the extension if different from filetype in url extension", () => { + it("does not strip the extension if different from filetype in url extension", async () => { const data = { ...requiredFields, url: "https://example.com/image.png", diff --git a/frontend/test/unit/specs/utils/deep-freeze.spec.js b/frontend/test/unit/specs/utils/deep-freeze.spec.js index cb3fb5e88e2..1cefc5a11f3 100644 --- a/frontend/test/unit/specs/utils/deep-freeze.spec.js +++ b/frontend/test/unit/specs/utils/deep-freeze.spec.js @@ -1,37 +1,37 @@ import { deepFreeze } from "~/utils/deep-freeze" describe("deepFreeze", () => { - it("should freeze shallow arrays", () => { + it("should freeze shallow arrays", async () => { const a = [1, 2] deepFreeze(a) expect(() => (a[0] = "a")).toThrow() }) - it("should freeze shallow objects", () => { + it("should freeze shallow objects", async () => { const a = { a: 1, b: 2 } deepFreeze(a) expect(() => (a.a = 5)).toThrow() }) - it("should freeze nested arrays", () => { + it("should freeze nested arrays", async () => { const a = [[2], 5] deepFreeze(a) expect(() => (a[0][0] = "a")).toThrow() }) - it("should freeze nested objects", () => { + it("should freeze nested objects", async () => { const a = { a: { b: 2 } } deepFreeze(a) expect(() => (a.a.b = 5)).toThrow() }) - it("should freeze objects inside of an array", () => { + it("should freeze objects inside of an array", async () => { const a = [[{ a: "b" }]] deepFreeze(a) expect(() => (a[0][0].a = "1")).toThrow() }) - it("should freeze arrays inside of objects", () => { + it("should freeze arrays inside of objects", async () => { const a = { b: [1] } deepFreeze(a) expect(() => a.b.push(3)).toThrow() diff --git a/frontend/test/unit/specs/utils/resampling.spec.js b/frontend/test/unit/specs/utils/resampling.spec.js index f6ae4ca135d..4cfecaed580 100644 --- a/frontend/test/unit/specs/utils/resampling.spec.js +++ b/frontend/test/unit/specs/utils/resampling.spec.js @@ -3,11 +3,11 @@ import { downsampleArray, upsampleArray } from "~/utils/resampling" describe("upsampleArray", () => { const baseArray = [0, 10, 30, 20] - it("should scale up array by filling points between elements", () => { + it("should scale up array by filling points between elements", async () => { expect(upsampleArray(baseArray, 7)).toEqual([0, 5, 10, 20, 30, 25, 20]) }) - it("should scale up array even when not exactly divisible", () => { + it("should scale up array even when not exactly divisible", async () => { const upsampledArray = upsampleArray(baseArray, 8) expect(upsampledArray[0]).toBe(0) expect(upsampledArray[1]).toBeCloseTo(4.285, 2) @@ -23,11 +23,11 @@ describe("upsampleArray", () => { describe("downsampleArray", () => { const baseArray = [5, 10, 15, 10, 5, 0, 5] - it("should scale down array by dropping points between elements", () => { + it("should scale down array by dropping points between elements", async () => { expect(downsampleArray(baseArray, 4)).toEqual([5, 15, 0, 5]) }) - it("should scale down array even when not exactly divisible", () => { + it("should scale down array even when not exactly divisible", async () => { expect(downsampleArray(baseArray, 3)).toEqual([5, 15, 5]) }) }) diff --git a/frontend/test/unit/specs/utils/string-to-boolean.spec.js b/frontend/test/unit/specs/utils/string-to-boolean.spec.js index 64e1a4c2c87..fbf6d85d996 100644 --- a/frontend/test/unit/specs/utils/string-to-boolean.spec.js +++ b/frontend/test/unit/specs/utils/string-to-boolean.spec.js @@ -1,24 +1,24 @@ import { stringToBoolean } from "~/utils/string-to-boolean" describe("stringToBoolean", () => { - it('returns true for "true", "yes" and "1"', () => { + it('returns true for "true", "yes" and "1"', async () => { expect(stringToBoolean("true")).toBe(true) expect(stringToBoolean("yes")).toBe(true) expect(stringToBoolean("1")).toBe(true) }) - it('returns false for "false", "no" and "0"', () => { + it('returns false for "false", "no" and "0"', async () => { expect(stringToBoolean("false")).toBe(false) expect(stringToBoolean("no")).toBe(false) expect(stringToBoolean("0")).toBe(false) }) - it("returns booleans as-is", () => { + it("returns booleans as-is", async () => { expect(stringToBoolean(true)).toBe(true) expect(stringToBoolean(false)).toBe(false) }) - it("converts null-ish values to false", () => { + it("converts null-ish values to false", async () => { expect(stringToBoolean(null)).toBe(false) expect(stringToBoolean(undefined)).toBe(false) }) diff --git a/frontend/test/unit/specs/utils/time-fmt.spec.ts b/frontend/test/unit/specs/utils/time-fmt.spec.ts index bbd3cc101c7..fb1c675bf6b 100644 --- a/frontend/test/unit/specs/utils/time-fmt.spec.ts +++ b/frontend/test/unit/specs/utils/time-fmt.spec.ts @@ -3,25 +3,25 @@ import { describe, expect, it } from "vitest" import { timeFmt } from "~/utils/time-fmt" describe("timeFmt", () => { - it("omits hours if zero", () => { + it("omits hours if zero", async () => { expect(timeFmt(59)).toBe("0:59") expect(timeFmt(60)).toBe("1:00") expect(timeFmt(61)).toBe("1:01") }) - it("handles zero minutes", () => { + it("handles zero minutes", async () => { expect(timeFmt(59)).toBe("0:59") expect(timeFmt(3600)).toBe("1:00:00") expect(timeFmt(3659)).toBe("1:00:59") }) - it("handles zero seconds", () => { + it("handles zero seconds", async () => { expect(timeFmt(60)).toBe("1:00") expect(timeFmt(3600)).toBe("1:00:00") expect(timeFmt(3660)).toBe("1:01:00") }) - it("pads minutes if hours present", () => { + it("pads minutes if hours present", async () => { expect(timeFmt(3661)).toBe("1:01:01") }) diff --git a/frontend/test/unit/test-utils/pinia.js b/frontend/test/unit/test-utils/pinia.js index da09bd5c146..3b00ac7cf6b 100644 --- a/frontend/test/unit/test-utils/pinia.js +++ b/frontend/test/unit/test-utils/pinia.js @@ -1,31 +1,7 @@ // eslint-disable-next-line no-restricted-imports import * as pinia from "pinia" -import { vi } from "vitest" -export const createPinia = () => - pinia.createPinia().use(() => ({ - $nuxt: { - $openverseApiToken: "", - $sentry: { - captureException: vi.fn(), - captureEvent: vi.fn(), - }, - $cookies: { - set: vi.fn(), - get: vi.fn(), - setAll: vi.fn(), - }, - i18n: { - localeProperties: { - code: "es", - translated: 100, - name: "Spanish", - }, - }, - error: vi.fn(), - localePath: vi.fn(), - }, - })) +export const createPinia = () => pinia.createPinia() export const setActivePinia = pinia.setActivePinia diff --git a/frontend/test/unit/test-utils/render-suspended.ts b/frontend/test/unit/test-utils/render-suspended.ts new file mode 100644 index 00000000000..311778f5cfb --- /dev/null +++ b/frontend/test/unit/test-utils/render-suspended.ts @@ -0,0 +1,185 @@ +// 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 + ) + } + }, +}) + +// @ts-expect-error virtual file +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: (renderContext: any) => + // 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 b0a415699cb..b5f9f100a96 100644 --- a/frontend/test/unit/test-utils/render.js +++ b/frontend/test/unit/test-utils/render.js @@ -1,5 +1,13 @@ -import { renderSuspended } from "@nuxt/test-utils/runtime" +import { defu } from "defu" -export const render = (Component, options = {}) => { - return renderSuspended(Component, options) +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, { + global: { + plugins: [i18n], + }, + }) + return await renderSuspended(Component, options) }