feat: complete dockerization (optional)

This commit is contained in:
fsylvain committed 2026-10-05 04:52:49 +02:00
1 parent 3e01c29657
commit d275ba3c14
38 files changed
+1244 -20

No files matched your search

@@ -0,0 +1,150 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import uiPlugin from '@nuxt/ui/vue-plugin'
import router from '@/router'
import { useApi, type UserRole } from '@/composables/useApi'
import type { UpdateStatus } from '@/composables/useUpdate'
import { useSessionStore } from '@/stores/session'
import SettingsView from '../SettingsView.vue'
import {
RUNNING_VERSION,
STATUS_PATH,
findButton,
newerAvailable,
stubClipboard,
upToDate,
visibleButtonLabels,
} from './updateTestKit'
vi.mock('@/composables/useApi', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/composables/useApi')>()
return {
...actual,
useApi: vi.fn(),
}
})
const CONTAINER_COMMAND = 'docker compose pull && docker compose up -d'
let wrapper: VueWrapper | null = null
async function mountSettings(role: UserRole, status: UpdateStatus) {
setActivePinia(createPinia())
const session = useSessionStore()
session.session = { verified: true, username: 'marta', id: 1, role }
session.status = 'ready'
const request = vi.fn((path: string) => {
if (path === '/v1/settings/') {
return Promise.resolve({ version: RUNNING_VERSION })
}
if (path === STATUS_PATH) {
return Promise.resolve(status)
}
throw new Error(`Unexpected request: ${path}`)
})
vi.mocked(useApi).mockReturnValue({
getMe: vi.fn().mockResolvedValue({
verified: true,
username: 'marta',
id: 1,
displayName: '',
role,
}),
request,
})
await router.push('/settings')
wrapper = mount(SettingsView, {
attachTo: document.body,
global: { plugins: [router, uiPlugin] },
})
await flushPromises()
}
function updatesCard() {
return document.body.querySelector('[data-testid="updates-card"]')!
}
function cardText() {
return updatesCard().textContent ?? ''
}
afterEach(async () => {
wrapper?.unmount()
wrapper = null
vi.useRealTimers()
await router.push('/login')
document.body.innerHTML = ''
})
describe('Settings, owner, container, update available', () => {
it('shows the pull-image guidance, the command, Copy and the docs link, with no other button', async () => {
await mountSettings('owner', newerAvailable({ container: true }))
expect(cardText()).toContain('Version 2.3.0-beta is available')
expect(cardText()).toContain(
'You have v2.2.0-beta. This site runs in Docker, so it updates by pulling a new image. Run the commands below on the server.',
)
expect(updatesCard().querySelector('code')?.textContent).toBe(CONTAINER_COMMAND)
const link = Array.from(updatesCard().querySelectorAll('a')).find(
(anchor) => anchor.textContent?.trim() === 'How to update',
)!
expect(link.getAttribute('href')).toBe(
'https://git.example/fsylvain/RenewCMS/src/branch/main/docs/docker.md',
)
expect(visibleButtonLabels(updatesCard())).toEqual(["What's new in 2.3.0-beta", 'Copy'])
expect(cardText()).not.toContain('renewcms enable-browser-updates')
expect(cardText()).not.toContain('They run this once on the server')
expect(cardText()).not.toContain("can't update itself from the browser")
})
it('copies the container command and reads Copied for 2 s', async () => {
const writeText = stubClipboard()
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
await mountSettings('owner', newerAvailable({ container: true }))
findButton('Copy')!.click()
await flushPromises()
expect(writeText).toHaveBeenCalledWith(CONTAINER_COMMAND)
expect(findButton('Copied')!.getAttribute('aria-live')).toBe('polite')
await vi.advanceTimersByTimeAsync(2000)
expect(findButton('Copy')?.textContent?.trim()).toBe('Copy')
expect(findButton('Copied')).toBeUndefined()
})
})
describe('Settings, owner, container, up to date', () => {
it("shows You're up to date with only Check for updates and no container hint", async () => {
await mountSettings('owner', upToDate({ container: true }))
expect(cardText()).toContain("You're up to date")
expect(visibleButtonLabels(updatesCard())).toEqual(['Check for updates'])
expect(cardText()).not.toContain('Docker')
expect(updatesCard().querySelector('code')).toBeNull()
})
})
describe('Settings, admin, container', () => {
it('keeps the admin view with no command block', async () => {
await mountSettings('admin', newerAvailable({ container: true }))
expect(cardText()).toContain('Only the owner can update RenewCMS.')
expect(cardText()).not.toContain('Docker')
expect(updatesCard().querySelector('code')).toBeNull()
expect(visibleButtonLabels(updatesCard())).toEqual([])
})
})
describe('Settings, owner, not container, not prepared', () => {
it('still shows the enable-browser-updates command', async () => {
await mountSettings('owner', newerAvailable({ container: false }))
expect(updatesCard().querySelector('code')?.textContent).toBe(
'renewcms enable-browser-updates',
)
expect(cardText()).toContain("This site can't update itself from the browser yet.")
expect(cardText()).not.toContain('Docker')
})
})
+1
View File
@@ -37,6 +37,7 @@ export function upToDate(overrides: Partial<UpdateStatus> = {}): UpdateStatus {
error: null,
started_at: null,
finished_at: null,
container: false,
...overrides,
}
}