Archipelago — open-source initial import

This commit is contained in:
Archipelago
2026-08-12 10:55:50 +00:00
commit 76cfd55971
1578 changed files with 332870 additions and 0 deletions
@@ -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')
})
})