Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import AppContentSection from '../AppContentSection.vue'
|
||||
import { PackageState, type PackageDataEntry } from '@/types/api'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
appDetails: {
|
||||
about: 'About {name}',
|
||||
features: 'Features',
|
||||
screenshots: 'Screenshots',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function packageData(overrides: Partial<PackageDataEntry> = {}): PackageDataEntry {
|
||||
return {
|
||||
state: PackageState.Running,
|
||||
health: 'healthy',
|
||||
manifest: {
|
||||
id: 'example',
|
||||
title: 'Example',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Example app',
|
||||
long: 'Example app details',
|
||||
},
|
||||
'release-notes': '',
|
||||
license: '',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': '',
|
||||
'donation-url': null,
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function mountContent(pkg: PackageDataEntry) {
|
||||
return mount(AppContentSection, {
|
||||
props: {
|
||||
pkg,
|
||||
features: [],
|
||||
needsBitcoinSync: false,
|
||||
bitcoinSynced: true,
|
||||
bitcoinSyncPercent: 100,
|
||||
bitcoinBlockHeight: 100,
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('AppContentSection', () => {
|
||||
it('does not show screenshot placeholders when no screenshots exist', () => {
|
||||
const wrapper = mountContent(packageData())
|
||||
|
||||
expect(wrapper.text()).not.toContain('Screenshots')
|
||||
expect(wrapper.findAll('img')).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('renders real screenshot metadata when provided', () => {
|
||||
const wrapper = mountContent(packageData({
|
||||
manifest: {
|
||||
...packageData().manifest,
|
||||
screenshots: [
|
||||
'/assets/screenshots/example-dashboard.png',
|
||||
{ src: '/assets/screenshots/example-settings.png', alt: 'Settings screen' },
|
||||
' ',
|
||||
],
|
||||
},
|
||||
}))
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
|
||||
expect(wrapper.text()).toContain('Screenshots')
|
||||
expect(images).toHaveLength(2)
|
||||
expect(images[0]?.attributes('src')).toBe('/assets/screenshots/example-dashboard.png')
|
||||
expect(images[0]?.attributes('alt')).toBe('Example screenshot 1')
|
||||
expect(images[1]?.attributes('src')).toBe('/assets/screenshots/example-settings.png')
|
||||
expect(images[1]?.attributes('alt')).toBe('Settings screen')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import AppHeroSection from '../AppHeroSection.vue'
|
||||
import { PackageState, type PackageDataEntry } from '@/types/api'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
common: {
|
||||
launch: 'Launch',
|
||||
restart: 'Restart',
|
||||
start: 'Start',
|
||||
stop: 'Stop',
|
||||
uninstall: 'Uninstall',
|
||||
},
|
||||
appDetails: {
|
||||
channels: 'Channels',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function packageData(overrides: Partial<PackageDataEntry> = {}): PackageDataEntry {
|
||||
return {
|
||||
state: PackageState.Running,
|
||||
health: 'healthy',
|
||||
manifest: {
|
||||
id: 'example',
|
||||
title: 'Example',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Example app',
|
||||
long: 'Example app',
|
||||
},
|
||||
'release-notes': '',
|
||||
license: '',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': '',
|
||||
'donation-url': null,
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function mountHero(props: Partial<InstanceType<typeof AppHeroSection>['$props']> = {}) {
|
||||
return mount(AppHeroSection, {
|
||||
props: {
|
||||
pkg: packageData(),
|
||||
appId: 'example',
|
||||
packageKey: 'example',
|
||||
canLaunch: true,
|
||||
isWebOnly: false,
|
||||
pendingAction: null,
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('AppHeroSection', () => {
|
||||
it('disables app controls while a container action is running', () => {
|
||||
const wrapper = mountHero({ pendingAction: 'restart' })
|
||||
|
||||
expect(wrapper.text()).toContain('Restarting...')
|
||||
expect(wrapper.findAll('button').every(button => button.attributes('disabled') !== undefined)).toBe(true)
|
||||
})
|
||||
|
||||
it('labels update progress and disables update controls', () => {
|
||||
const wrapper = mountHero({
|
||||
pendingAction: 'update',
|
||||
pkg: packageData({ 'available-update': '1.1.0' }),
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Updating...')
|
||||
const updateButtons = wrapper.findAll('button').filter(button => button.text().includes('Updating...'))
|
||||
expect(updateButtons.length).toBeGreaterThan(0)
|
||||
expect(updateButtons.every(button => button.attributes('disabled') !== undefined)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,135 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import AppSidebar from '../AppSidebar.vue'
|
||||
import { PackageState } from '@/types/api'
|
||||
|
||||
function mountSidebar(manifestOverrides: Record<string, unknown>, propOverrides: Record<string, unknown> = {}) {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
appDetails: {
|
||||
access: 'Access',
|
||||
documentation: 'Documentation',
|
||||
gateway: 'Gateway',
|
||||
information: 'Information',
|
||||
lan: 'LAN',
|
||||
links: 'Links',
|
||||
ram: 'RAM',
|
||||
ramDesc: 'Memory required',
|
||||
requirements: 'Requirements',
|
||||
setupInstructions: 'Setup Instructions',
|
||||
requiresTor: 'Requires Tor',
|
||||
services: 'Services',
|
||||
sourceCode: 'Source Code',
|
||||
storage: 'Storage',
|
||||
storageDesc: 'Storage required',
|
||||
tor: 'Tor',
|
||||
website: 'Website',
|
||||
},
|
||||
common: {
|
||||
category: 'Category',
|
||||
developer: 'Developer',
|
||||
license: 'License',
|
||||
status: 'Status',
|
||||
version: 'Version',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return mount(AppSidebar, {
|
||||
props: {
|
||||
pkg: {
|
||||
state: PackageState.Running,
|
||||
manifest: {
|
||||
id: 'example',
|
||||
title: 'Example',
|
||||
version: '1.0.0',
|
||||
license: '',
|
||||
...manifestOverrides,
|
||||
},
|
||||
'static-files': { license: '', instructions: 'Follow step 1.\nThen step 2.', icon: '' },
|
||||
},
|
||||
packageKey: 'example',
|
||||
isWebOnly: false,
|
||||
gatewayState: 'stopped',
|
||||
interfaceAddresses: null,
|
||||
lanUrl: '',
|
||||
torUrl: '',
|
||||
showTorAddress: false,
|
||||
credentials: null,
|
||||
credentialsLoading: false,
|
||||
...propOverrides,
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('AppSidebar', () => {
|
||||
it('renders manifest links with real URLs', () => {
|
||||
const wrapper = mountSidebar({
|
||||
website: 'https://example.com',
|
||||
'upstream-repo': 'https://github.com/example/app',
|
||||
'support-site': 'https://docs.example.com',
|
||||
})
|
||||
|
||||
const hrefs = wrapper.findAll('a').map(anchor => anchor.attributes('href'))
|
||||
|
||||
expect(hrefs).toEqual([
|
||||
'https://example.com',
|
||||
'https://github.com/example/app',
|
||||
'https://docs.example.com',
|
||||
])
|
||||
expect(wrapper.text()).toContain('Website')
|
||||
expect(wrapper.text()).toContain('Source Code')
|
||||
expect(wrapper.text()).toContain('Documentation')
|
||||
})
|
||||
|
||||
it('does not render dead placeholder links', () => {
|
||||
const wrapper = mountSidebar({
|
||||
website: '#',
|
||||
'upstream-repo': '',
|
||||
'support-site': undefined,
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('a')).toHaveLength(0)
|
||||
expect(wrapper.text()).not.toContain('Links')
|
||||
})
|
||||
|
||||
it('shows a credentials loading state before credentials arrive', () => {
|
||||
const wrapper = mountSidebar({}, { credentialsLoading: true })
|
||||
|
||||
expect(wrapper.text()).toContain('Credentials')
|
||||
expect(wrapper.text()).toContain('Loading credentials...')
|
||||
})
|
||||
|
||||
it('renders setup instructions when provided', () => {
|
||||
const wrapper = mountSidebar({})
|
||||
|
||||
expect(wrapper.text()).toContain('Setup Instructions')
|
||||
expect(wrapper.text()).toContain('Follow step 1.')
|
||||
expect(wrapper.text()).toContain('Then step 2.')
|
||||
})
|
||||
|
||||
it('hides setup instructions when empty', () => {
|
||||
const wrapper = mountSidebar({}, {
|
||||
pkg: {
|
||||
state: PackageState.Running,
|
||||
manifest: {
|
||||
id: 'example',
|
||||
title: 'Example',
|
||||
version: '1.0.0',
|
||||
license: '',
|
||||
},
|
||||
'static-files': { license: '', instructions: ' ', icon: '' },
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.text()).not.toContain('Setup Instructions')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user