diff --git a/packages/web-app-app-store/src/LayoutContainer.vue b/packages/web-app-app-store/src/LayoutContainer.vue
index 87497b61bed..20a2750d219 100644
--- a/packages/web-app-app-store/src/LayoutContainer.vue
+++ b/packages/web-app-app-store/src/LayoutContainer.vue
@@ -2,7 +2,7 @@
-
+
diff --git a/packages/web-app-app-store/src/components/AppAuthors.vue b/packages/web-app-app-store/src/components/AppAuthors.vue
index f927d6cc390..e8b78f96178 100644
--- a/packages/web-app-app-store/src/components/AppAuthors.vue
+++ b/packages/web-app-app-store/src/components/AppAuthors.vue
@@ -1,15 +1,18 @@
diff --git a/packages/web-app-app-store/src/components/AppImageGallery.vue b/packages/web-app-app-store/src/components/AppImageGallery.vue
index b027dde8665..cea696fcbfa 100644
--- a/packages/web-app-app-store/src/components/AppImageGallery.vue
+++ b/packages/web-app-app-store/src/components/AppImageGallery.vue
@@ -11,12 +11,19 @@
-
-
+
-
-
-
+
diff --git a/packages/web-app-app-store/src/components/AppResources.vue b/packages/web-app-app-store/src/components/AppResources.vue
index 0638f243873..59b3950e0f3 100644
--- a/packages/web-app-app-store/src/components/AppResources.vue
+++ b/packages/web-app-app-store/src/components/AppResources.vue
@@ -1,16 +1,28 @@
diff --git a/packages/web-app-app-store/src/components/AppTags.vue b/packages/web-app-app-store/src/components/AppTags.vue
index db53a38adae..d3a37b76a32 100644
--- a/packages/web-app-app-store/src/components/AppTags.vue
+++ b/packages/web-app-app-store/src/components/AppTags.vue
@@ -3,6 +3,7 @@
{
- return props.app.versions.map((version) => {
- return {
- ...version,
- minOCIS: version.minOCIS ? `v${version.minOCIS}` : '-',
- id: version.version
- }
- })
+ return (props.app.versions || [])
+ .filter((version) => {
+ if (isEmpty(version.version) || isEmpty(version.url)) {
+ return false
+ }
+ try {
+ new URL(version.url)
+ } catch {
+ return false
+ }
+ return true
+ })
+ .map((version) => {
+ return {
+ ...version,
+ minOCIS: version.minOCIS ? `v${version.minOCIS}` : '-',
+ id: version.version
+ }
+ })
})
const fields = computed(() => {
return [
diff --git a/packages/web-app-app-store/src/types.ts b/packages/web-app-app-store/src/types.ts
index 6cfc6d6454c..29e4ae43d4c 100644
--- a/packages/web-app-app-store/src/types.ts
+++ b/packages/web-app-app-store/src/types.ts
@@ -23,12 +23,14 @@ export const AppBadgeSchema = z.object({
label: z.string(),
color: z.enum(BADGE_COLORS).optional().default('primary')
})
+export type AppBadge = z.infer
export const AppAuthorSchema = z.object({
name: z.string(),
email: z.string().optional(),
url: z.string().optional()
})
+export type AppAuthor = z.infer
export const AppImageSchema = z.object({
url: z.string(),
@@ -41,6 +43,7 @@ export const AppResourceSchema = z.object({
label: z.string(),
icon: z.string().optional()
})
+export type AppResource = z.infer
export const RawAppSchema = z.object({
id: z.string(),
diff --git a/packages/web-app-app-store/tests/unit/LayoutContainer.spec.ts b/packages/web-app-app-store/tests/unit/LayoutContainer.spec.ts
new file mode 100644
index 00000000000..c694a88d69d
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/LayoutContainer.spec.ts
@@ -0,0 +1,57 @@
+import LayoutContainer from '../../src/LayoutContainer.vue'
+import {
+ defaultComponentMocks,
+ defaultPlugins,
+ mount,
+ nextTicks
+} from '@ownclouders/web-test-helpers'
+import { useAppsStore } from '../../src/piniaStores'
+
+const selectors = {
+ loadingSpinner: '#app-loading-spinner',
+ routerView: '[data-testid="app-store-router-view"]'
+}
+
+describe('LayoutContainer', () => {
+ it('shows a loading spinner while apps are loading from repositories', () => {
+ const { wrapper } = getWrapper({ keepLoadingApps: true })
+ expect(wrapper.find(selectors.loadingSpinner).exists()).toBeTruthy()
+ expect(wrapper.find(selectors.routerView).exists()).toBeFalsy()
+ })
+ it('renders the router view when loading apps is done', async () => {
+ const { wrapper } = getWrapper({})
+ await nextTicks(2)
+ expect(wrapper.find(selectors.loadingSpinner).exists()).toBeFalsy()
+ expect(wrapper.find(selectors.routerView).exists()).toBeTruthy()
+ })
+})
+
+function getWrapper({ keepLoadingApps }: { keepLoadingApps?: boolean }) {
+ const plugins = defaultPlugins({})
+
+ const { loadApps } = useAppsStore()
+ vi.mocked(loadApps).mockReturnValue(
+ new Promise((res) => {
+ if (!keepLoadingApps) {
+ return res()
+ }
+ return setTimeout(() => res(), 500)
+ })
+ )
+
+ const mocks = {
+ ...defaultComponentMocks(),
+ loadApps
+ }
+
+ return {
+ mocks,
+ wrapper: mount(LayoutContainer, {
+ global: {
+ mocks,
+ provide: mocks,
+ plugins
+ }
+ })
+ }
+}
diff --git a/packages/web-app-app-store/tests/unit/components/AppActions.spec.ts b/packages/web-app-app-store/tests/unit/components/AppActions.spec.ts
new file mode 100644
index 00000000000..595eee41de2
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/components/AppActions.spec.ts
@@ -0,0 +1,54 @@
+import { defaultPlugins, mount } from '@ownclouders/web-test-helpers'
+import AppActions from '../../../src/components/AppActions.vue'
+import { App, AppVersion } from '../../../src/types'
+import { mock } from 'vitest-mock-extended'
+
+const version1: AppVersion = {
+ version: '1.0.0',
+ url: 'https://example.com/app-1.0.0.zip'
+}
+const version2: AppVersion = {
+ version: '1.1.0',
+ url: 'https://example.com/app-1.1.0.zip'
+}
+const versions = [version1, version2]
+const mostRecentVersion = version2
+
+const selectors = {
+ downloadButton: 'button'
+}
+
+describe('AppActions', () => {
+ it('renders a "Download" button', () => {
+ const { wrapper } = getWrapper({})
+ expect(wrapper.find(selectors.downloadButton).text()).toBe('Download')
+ })
+ describe('calling the "download" handler', () => {
+ it('uses the most recent version when none is specified', async () => {
+ const { wrapper } = getWrapper({})
+ await wrapper.find(selectors.downloadButton).trigger('click')
+ expect(window.location.href).toBe(mostRecentVersion.url)
+ })
+ it('uses the version provided via props', async () => {
+ const { wrapper } = getWrapper({ version: version1 })
+ await wrapper.find(selectors.downloadButton).trigger('click')
+ expect(window.location.href).toBe(version1.url)
+ })
+ })
+})
+
+const getWrapper = ({ version }: { version?: AppVersion }) => {
+ const app = { ...mock({}), versions, mostRecentVersion }
+
+ return {
+ wrapper: mount(AppActions, {
+ props: {
+ app,
+ version
+ },
+ global: {
+ plugins: [...defaultPlugins()]
+ }
+ })
+ }
+}
diff --git a/packages/web-app-app-store/tests/unit/components/AppAuthors.spec.ts b/packages/web-app-app-store/tests/unit/components/AppAuthors.spec.ts
new file mode 100644
index 00000000000..126fd7820ea
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/components/AppAuthors.spec.ts
@@ -0,0 +1,58 @@
+import AppAuthors from '../../../src/components/AppAuthors.vue'
+import { mock } from 'vitest-mock-extended'
+import { App, AppAuthor } from '../../../src/types'
+import { mount } from '@ownclouders/web-test-helpers'
+
+const author1: AppAuthor = {
+ name: 'John Doe',
+ url: 'https://johndoe.com'
+}
+const author2: AppAuthor = {
+ name: 'Jane Doe'
+}
+const author3: AppAuthor = {
+ name: 'Wololo Priest',
+ url: 'wololo'
+}
+const author4: AppAuthor = {
+ url: 'trololo'
+}
+const authors = [author1, author2, author3, author4]
+
+const selectors = {
+ item: '.app-author-item',
+ link: '[data-testid="author-link"]',
+ label: '[data-testid="author-label"]'
+}
+
+describe('AppAuthors.vue', () => {
+ it('renders only authors with name and valid or empty url', () => {
+ const { wrapper } = getWrapper()
+ expect(wrapper.findAll(selectors.item).length).toBe(2)
+ })
+ it('renders authors as link when they have a url and name', () => {
+ const { wrapper } = getWrapper()
+ const author = wrapper.findAll(selectors.item).at(0)
+ expect(author.exists()).toBeTruthy()
+ const link = author.find(selectors.link)
+ expect(link.exists()).toBeTruthy()
+ expect(link.attributes().href).toBe(author1.url)
+ expect(link.text()).toBe(author1.name)
+ })
+ it('renders authors as span when they only have a name', () => {
+ const { wrapper } = getWrapper()
+ const author = wrapper.findAll(selectors.item).at(1)
+ expect(author.find(selectors.link).exists()).toBeFalsy()
+ expect(author.find(selectors.label).text()).toBe(author2.name)
+ })
+})
+
+const getWrapper = () => {
+ const app = { ...mock({}), authors }
+
+ return {
+ wrapper: mount(AppAuthors, {
+ props: { app }
+ })
+ }
+}
diff --git a/packages/web-app-app-store/tests/unit/components/AppImageGallery.spec.ts b/packages/web-app-app-store/tests/unit/components/AppImageGallery.spec.ts
new file mode 100644
index 00000000000..956db5bdc2f
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/components/AppImageGallery.spec.ts
@@ -0,0 +1,164 @@
+import { defaultPlugins, mount } from '@ownclouders/web-test-helpers'
+import { mock } from 'vitest-mock-extended'
+import AppImageGallery from '../../../src/components/AppImageGallery.vue'
+import { App, AppBadge, AppImage, BADGE_COLORS } from '../../../src/types'
+
+const coverImageWithUrl: AppImage = {
+ url: 'https://example.com/cover.jpg',
+ caption: 'Cover image'
+}
+const coverImageWithoutUrl: AppImage = {
+ caption: 'Trololo'
+}
+const screenshot1: AppImage = {
+ url: 'https://example.com/screenshot1.jpg',
+ caption: 'Screenshot 1'
+}
+const screenshot2: AppImage = {
+ url: 'https://example.com/screenshot2.jpg',
+ caption: 'Screenshot 2'
+}
+const screenshot3: AppImage = {
+ url: 'https://example.com/screenshot3.jpg',
+ caption: 'Screenshot 3'
+}
+const screenshots = [screenshot1, screenshot2, screenshot3]
+
+const selectors = {
+ badge: '.app-image-ribbon',
+ image: '.app-image img',
+ imageFallback: '.app-image .fallback-icon',
+ pagination: '.app-image-navigation',
+ paginationPrev: '[data-testid="prev-image"]',
+ paginationNext: '[data-testid="next-image"]',
+ paginationSet: '[data-testid="set-image"]'
+}
+
+describe('AppImageGallery.vue', () => {
+ describe('badges', () => {
+ it('renders no ribbon if the app has no badge', () => {
+ const { wrapper } = getWrapper({})
+ expect(wrapper.find(selectors.badge).exists()).toBeFalsy()
+ })
+ it('renders a ribbon if the app has a badge', () => {
+ const badge = { label: 'New', color: BADGE_COLORS[1] }
+ const { wrapper } = getWrapper({ badge })
+ expect(wrapper.find(selectors.badge).exists()).toBeTruthy()
+ expect(wrapper.find(selectors.badge).text()).toBe(badge.label)
+ expect(
+ wrapper.find(selectors.badge).element.className.includes(`app-image-ribbon-${badge.color}`)
+ ).toBeTruthy()
+ })
+ })
+ describe('current image', () => {
+ it('renders oc-img if the image has a url', () => {
+ const { wrapper } = getWrapper({ coverImage: coverImageWithUrl })
+ expect(wrapper.find(selectors.image).exists()).toBeTruthy()
+ expect(wrapper.find(selectors.image).attributes().src).toBe(coverImageWithUrl.url)
+ expect(wrapper.find(selectors.imageFallback).exists()).toBeFalsy()
+ })
+ it('renders oc-icon if the image has no url', () => {
+ const { wrapper } = getWrapper({ coverImage: coverImageWithoutUrl })
+ expect(wrapper.find(selectors.image).exists()).toBeFalsy()
+ expect(wrapper.find(selectors.imageFallback).exists()).toBeTruthy()
+ })
+ it('renders oc-icon if the app has no coverImage field', () => {
+ const { wrapper } = getWrapper({ coverImage: null })
+ expect(wrapper.find(selectors.image).exists()).toBeFalsy()
+ expect(wrapper.find(selectors.imageFallback).exists()).toBeTruthy()
+ })
+ })
+ describe('navigation', () => {
+ it('has no navigation if there is only a single image', () => {
+ const { wrapper } = getWrapper({ showPagination: true, coverImage: coverImageWithUrl })
+ expect(wrapper.find(selectors.pagination).exists()).toBeFalsy()
+ })
+ it('has no navigation if it is disabled via prop', () => {
+ const { wrapper } = getWrapper({
+ showPagination: false,
+ coverImage: coverImageWithUrl,
+ screenshots
+ })
+ expect(wrapper.find(selectors.pagination).exists()).toBeFalsy()
+ })
+ describe('is visible', () => {
+ it('has a pagination container', () => {
+ const { wrapper } = getWrapper({
+ showPagination: true,
+ coverImage: coverImageWithUrl,
+ screenshots
+ })
+ expect(wrapper.find(selectors.pagination).exists()).toBeTruthy()
+ })
+ it('has a prev button which cycles through images backwards', async () => {
+ const { wrapper } = getWrapper({
+ showPagination: true,
+ coverImage: coverImageWithUrl,
+ screenshots
+ })
+ const button = wrapper.find(selectors.paginationPrev)
+ expect(button.exists()).toBeTruthy()
+ const images = [coverImageWithUrl, ...screenshots]
+ for (let i = 1; i <= images.length; i++) {
+ await button.trigger('click')
+ expect(wrapper.find(selectors.image).attributes().src).toBe(images[images.length - i].url)
+ }
+ })
+ it('has a next button which cycles through images forward', async () => {
+ const { wrapper } = getWrapper({
+ showPagination: true,
+ coverImage: coverImageWithUrl,
+ screenshots
+ })
+ const button = wrapper.find(selectors.paginationNext)
+ const images = [coverImageWithUrl, ...screenshots]
+ expect(button.exists()).toBeTruthy()
+ for (let i = 1; i <= images.length; i++) {
+ await button.trigger('click')
+ expect(wrapper.find(selectors.image).attributes().src).toBe(images[i % images.length].url)
+ }
+ })
+ it('has a set-button per image which changes the current image', async () => {
+ const { wrapper } = getWrapper({
+ showPagination: true,
+ coverImage: coverImageWithUrl,
+ screenshots
+ })
+ const buttons = wrapper.findAll(selectors.paginationSet)
+ const images = [coverImageWithUrl, ...screenshots]
+ expect(buttons.length).toBe(images.length)
+ const indices = [2, 0, 1, 3, 3, 3, 0]
+ for (let i = 0; i < indices.length; i++) {
+ await buttons[indices[i]].trigger('click')
+ expect(wrapper.find(selectors.image).attributes().src).toBe(images[indices[i]].url)
+ }
+ })
+ })
+ })
+})
+
+const getWrapper = ({
+ showPagination,
+ badge,
+ coverImage,
+ screenshots = []
+}: {
+ showPagination?: boolean
+ badge?: AppBadge
+ coverImage?: AppImage
+ screenshots?: AppImage[]
+}) => {
+ const app = { ...mock({}), badge, coverImage, screenshots }
+
+ return {
+ wrapper: mount(AppImageGallery, {
+ props: {
+ app,
+ showPagination
+ },
+ global: {
+ plugins: defaultPlugins()
+ }
+ })
+ }
+}
diff --git a/packages/web-app-app-store/tests/unit/components/AppResources.spec.ts b/packages/web-app-app-store/tests/unit/components/AppResources.spec.ts
new file mode 100644
index 00000000000..98e6eb72f7f
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/components/AppResources.spec.ts
@@ -0,0 +1,63 @@
+import { mount } from '@ownclouders/web-test-helpers'
+import AppResources from '../../../src/components/AppResources.vue'
+import { App, AppResource } from '../../../src/types'
+import { mock } from 'vitest-mock-extended'
+
+const resource1: AppResource = {
+ url: 'https://trololo.whatever',
+ icon: 'github',
+ label: 'GitHub'
+}
+const resource2: AppResource = {
+ url: 'https://wololo',
+ label: 'Wololo'
+}
+const resource3: AppResource = {
+ url: 'https://some.url',
+ icon: 'file'
+}
+const resource4: AppResource = {
+ label: 'Wololo'
+}
+const resources = [resource1, resource2, resource3, resource4]
+
+const selectors = {
+ link: '[data-testid="resource-link"]',
+ icon: '[data-testid="resource-icon"]',
+ label: '[data-testid="resource-label"]'
+}
+
+describe('AppResources.vue', () => {
+ it('renders only resources with a valid URL and a label', () => {
+ const { wrapper } = getWrapper()
+ expect(wrapper.findAll(selectors.link).length).toBe(2)
+ })
+ it('renders a link per resource including an icon if present', () => {
+ const { wrapper } = getWrapper()
+
+ const link1 = wrapper.findAll(selectors.link).at(0)
+ expect(link1.exists()).toBeTruthy()
+ expect(link1.attributes().href).toBe(resource1.url)
+ expect(link1.find(selectors.label).text()).toBe(resource1.label)
+ expect(link1.find(selectors.icon).exists()).toBeTruthy()
+ if (link1.find(selectors.icon).exists()) {
+ expect(link1.find(selectors.icon).attributes().name).toBe(resource1.icon)
+ }
+
+ const link2 = wrapper.findAll(selectors.link).at(1)
+ expect(link2.exists()).toBeTruthy()
+ expect(link2.attributes().href).toBe(resource2.url)
+ expect(link2.find(selectors.label).text()).toBe(resource2.label)
+ expect(link2.find(selectors.icon).exists()).toBeFalsy()
+ })
+})
+
+const getWrapper = () => {
+ const app = { ...mock({}), resources }
+
+ return {
+ wrapper: mount(AppResources, {
+ props: { app }
+ })
+ }
+}
diff --git a/packages/web-app-app-store/tests/unit/components/AppTags.spec.ts b/packages/web-app-app-store/tests/unit/components/AppTags.spec.ts
new file mode 100644
index 00000000000..78f4cab5083
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/components/AppTags.spec.ts
@@ -0,0 +1,46 @@
+import AppTags from '../../../src/components/AppTags.vue'
+import { mount } from '@ownclouders/web-test-helpers'
+import { mock } from 'vitest-mock-extended'
+import { App } from '../../../src/types'
+
+const tags: string[] = ['someTag', 'anotherTag', 'wololo-tag']
+
+const selectors = {
+ button: '[data-testid="tag-button"]',
+ markElement: '.mark-element'
+}
+
+describe('AppTags.vue', () => {
+ it('renders one button per tag', () => {
+ const { wrapper } = getWrapper()
+ expect(wrapper.findAll(selectors.button)).toHaveLength(tags.length)
+ })
+ it('shows the tag text as button text', () => {
+ const { wrapper } = getWrapper()
+ const buttons = wrapper.findAll(selectors.button)
+ for (let i = 0; i < buttons.length; i++) {
+ expect(buttons[i].text()).toBe(tags[i])
+ }
+ })
+ it('emits click event on tag click', () => {
+ const { wrapper } = getWrapper()
+ wrapper.find(selectors.button).trigger('click')
+ expect(wrapper.emitted('click')).toBeTruthy()
+ })
+ it('applies mark-element css class to tag text for highlighting', () => {
+ const { wrapper } = getWrapper()
+ wrapper.findAll(selectors.button).forEach((button) => {
+ expect(button.find(selectors.markElement).exists()).toBeTruthy()
+ })
+ })
+})
+
+const getWrapper = () => {
+ const app = { ...mock({}), tags }
+
+ return {
+ wrapper: mount(AppTags, {
+ props: { app }
+ })
+ }
+}
diff --git a/packages/web-app-app-store/tests/unit/components/AppVersions.spec.ts b/packages/web-app-app-store/tests/unit/components/AppVersions.spec.ts
new file mode 100644
index 00000000000..60d3079f447
--- /dev/null
+++ b/packages/web-app-app-store/tests/unit/components/AppVersions.spec.ts
@@ -0,0 +1,104 @@
+import { App, AppVersion } from '../../../src/types'
+import AppVersions from '../../../src/components/AppVersions.vue'
+import { defaultPlugins, mount } from '@ownclouders/web-test-helpers'
+import { mock } from 'vitest-mock-extended'
+
+const version1: AppVersion = {
+ url: 'https://wololo.com/download-1.0.0.zip',
+ version: '1.0.0'
+}
+const version2: AppVersion = {
+ url: 'https://wololo.com/download-1.1.0.zip',
+ version: '1.1.0',
+ minOCIS: '6.5.0'
+}
+const version3: AppVersion = {
+ url: 'https://wololo.com/download-1.1.1.zip',
+ version: '1.1.1',
+ minOCIS: '6.5.0'
+}
+const version4: AppVersion = {
+ url: 'wololo',
+ version: '1.2.0'
+}
+const version5: AppVersion = {
+ url: 'https://wololo.com/download-1.3.0.zip',
+ minOCIS: '6.5.0'
+}
+const validVersions = [version1, version2, version3]
+const versions = [...validVersions, version4, version5]
+const mostRecentVersion = version2
+
+const selectors = {
+ versionRow: 'tbody tr',
+ tableCellVersion: '.oc-table-data-cell-version',
+ tableCellMinOCIS: '.oc-table-data-cell-minOCIS',
+ tableCellActions: '.oc-table-data-cell-actions',
+ downloadButton: '[data-testid="action-handler"]'
+}
+
+describe('AppVersions.vue', () => {
+ it('renders only versions which have at least a version and a valid URL', () => {
+ const { wrapper } = getWrapper()
+ const rows = wrapper.findAll(selectors.versionRow)
+ expect(rows).toHaveLength(validVersions.length)
+ rows.forEach((row, index) => {
+ const versionCell = row.find(selectors.tableCellVersion)
+ expect(versionCell.exists()).toBeTruthy()
+ expect(versionCell.text().startsWith(`v${versions[index].version}`)).toBeTruthy()
+ })
+ })
+ it('adds a "most recent" tag only to the latest version', () => {
+ const { wrapper } = getWrapper()
+ const rows = wrapper.findAll(selectors.versionRow)
+ rows.forEach((row, index) => {
+ const versionCell = row.find(selectors.tableCellVersion)
+ expect(versionCell.exists()).toBeTruthy()
+ if (versions[index].version === mostRecentVersion.version) {
+ expect(versionCell.text().startsWith(`v${versions[index].version}`)).toBeTruthy()
+ expect(versionCell.text().endsWith('most recent')).toBeTruthy()
+ } else {
+ expect(versionCell.text()).toBe(`v${versions[index].version}`)
+ }
+ })
+ })
+ it('renders the minimum required ocis version if present or "-" if not', () => {
+ const { wrapper } = getWrapper()
+ const rows = wrapper.findAll(selectors.versionRow)
+ rows.forEach((row, index) => {
+ const minOCISCell = row.find(selectors.tableCellMinOCIS)
+ expect(minOCISCell.exists()).toBeTruthy()
+ if (versions[index].minOCIS) {
+ expect(minOCISCell.text()).toBe(`v${versions[index].minOCIS}`)
+ } else {
+ expect(minOCISCell.text()).toBe('-')
+ }
+ })
+ })
+ it('renders a download button', async () => {
+ const { wrapper } = getWrapper()
+ const rows = wrapper.findAll(selectors.versionRow)
+ for (let i = 0; i < rows.length; i++) {
+ const actionsCell = rows[i].find(selectors.tableCellActions)
+ expect(actionsCell.exists()).toBeTruthy()
+ const downloadButton = actionsCell.find(selectors.downloadButton)
+ expect(downloadButton.exists()).toBeTruthy()
+ expect(downloadButton.text()).toBe('Download')
+ await downloadButton.trigger('click')
+ expect(wrapper.emitted('click')).toBeTruthy()
+ expect(window.location.href).toBe(versions[i].url)
+ }
+ })
+})
+
+const getWrapper = () => {
+ const app: App = { ...mock({}), versions, mostRecentVersion }
+ return {
+ wrapper: mount(AppVersions, {
+ props: { app },
+ global: {
+ plugins: [...defaultPlugins()]
+ }
+ })
+ }
+}