feat: integration screen

This commit is contained in:
fsylvain committed 2026-09-27 18:04:43 +02:00
1 parent a9a208b80a
commit ad55137883
13 files changed
+786 -6

No files matched your search

@@ -0,0 +1,189 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import uiPlugin from '@nuxt/ui/vue-plugin'
import router from '@/router'
import { useSessionStore } from '@/stores/session'
import EndpointCard from '@/components/EndpointCard.vue'
import IntegrationView from '../IntegrationView.vue'
const STUB_ORIGIN = 'https://cms.renew.example'
let currentWrapper: Awaited<ReturnType<typeof mount>> | null = null
function stubOrigin(origin: string) {
vi.stubGlobal('location', { ...window.location, origin })
}
function stubClipboard() {
const writeText = vi.fn().mockResolvedValue(undefined)
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
value: { writeText },
})
return writeText
}
async function mountIntegration() {
setActivePinia(createPinia())
const session = useSessionStore()
session.session = { verified: true, username: 'marta' }
session.status = 'ready'
await router.push('/integration')
const wrapper = mount(IntegrationView, {
attachTo: document.body,
global: {
plugins: [router, uiPlugin],
},
})
currentWrapper = wrapper
await flushPromises()
return wrapper
}
function findButton(wrapper: Awaited<ReturnType<typeof mount>>, name: string) {
return wrapper.findAll('button').find((button) => button.text().trim() === name)
}
afterEach(async () => {
currentWrapper?.unmount()
currentWrapper = null
vi.unstubAllGlobals()
vi.useRealTimers()
await router.push('/login')
})
describe("Default — the page loads with the app's own origin", () => {
it('renders the header, base URL, endpoint cards and notes with the stubbed origin', async () => {
stubOrigin(STUB_ORIGIN)
const wrapper = await mountIntegration()
expect(wrapper.text()).toContain('Connect your website')
expect(wrapper.text()).toContain('A guide for the developer who builds your public site.')
expect(wrapper.text()).toContain(
"RenewCMS is headless: it stores and publishes your articles but doesn't show them to visitors.",
)
expect(wrapper.text()).toContain(STUB_ORIGIN)
expect(findButton(wrapper, 'Copy')).toBeTruthy()
const cardTexts = wrapper.findAllComponents(EndpointCard).map((c) => c.text())
expect(cardTexts).toHaveLength(2)
expect(cardTexts[0]).toContain('/v1/article/')
expect(cardTexts[0]).toContain('Lists every online article.')
expect(cardTexts[0]).toContain(`${STUB_ORIGIN}/v1/article/`)
expect(cardTexts[1]).toContain('/v1/article/{id}')
expect(cardTexts[1]).toContain(
'Returns one online article. Drafts and unknown ids both return 404.',
)
expect(cardTexts[1]).toContain(`${STUB_ORIGIN}/v1/article/12`)
const fetchTabs = wrapper.findAll('[role="tab"]').filter((tab) => tab.text() === 'fetch')
expect(fetchTabs.length).toBeGreaterThan(0)
fetchTabs.forEach((tab) => {
expect(tab.attributes('aria-selected')).toBe('true')
})
expect(wrapper.text()).toContain(
'Anonymous callers only ever get online articles. Drafts stay private until you publish them.',
)
expect(wrapper.text()).toContain(
'Images in body use paths like /static/uploadedImages/<uuid>.png. Prefix them with the base URL before rendering.',
)
expect(wrapper.text()).toContain(
"Browsers can only call the API from origins listed in the server's CORS setting. Ask whoever runs the server to add your site's address.",
)
})
it('renders the breadcrumb Integration', async () => {
stubOrigin(STUB_ORIGIN)
const wrapper = await mountIntegration()
expect(wrapper.text()).toContain('Integration')
})
})
describe('Copied — the Base URL copy button', () => {
it('writes the origin to the clipboard, shows Copied for 2s, then reverts', async () => {
stubOrigin(STUB_ORIGIN)
const writeText = stubClipboard()
vi.useFakeTimers()
const wrapper = await mountIntegration()
const copyButton = findButton(wrapper, 'Copy')!
await copyButton.trigger('click')
await flushPromises()
expect(writeText).toHaveBeenCalledWith(STUB_ORIGIN)
expect(findButton(wrapper, 'Copied')).toBeTruthy()
expect(wrapper.find('[aria-live="polite"]').text()).toContain('Copied')
await vi.advanceTimersByTimeAsync(2000)
expect(findButton(wrapper, 'Copy')).toBeTruthy()
expect(findButton(wrapper, 'Copied')).toBeFalsy()
})
})
describe("Each EndpointCard's own Copy button is independent of the Base URL's", () => {
it("copies only that card's code and leaves the other buttons unaffected", async () => {
stubOrigin(STUB_ORIGIN)
const writeText = stubClipboard()
const wrapper = await mountIntegration()
const cards = wrapper.findAllComponents(EndpointCard)
const firstCardCopyButton = cards[0]
.findAll('button')
.find((button) => button.text().trim() === 'Copy')!
await firstCardCopyButton.trigger('click')
await flushPromises()
const expectedCode =
`const res = await fetch('${STUB_ORIGIN}/v1/article/')\n` +
'const articles = await res.json()'
expect(writeText).toHaveBeenCalledWith(expectedCode)
const copyButtons = wrapper.findAll('button').filter((button) => {
const text = button.text().trim()
return text === 'Copy' || text === 'Copied'
})
const copiedButtons = copyButtons.filter((button) => button.text().trim() === 'Copied')
expect(copiedButtons).toHaveLength(1)
const baseUrlCopyButton = findButton(wrapper, 'Copy')
expect(baseUrlCopyButton).toBeTruthy()
const secondCardCopyButton = cards[1]
.findAll('button')
.find((button) => button.text().trim() === 'Copy')
expect(secondCardCopyButton).toBeTruthy()
})
})
async function activateTab(container: HTMLElement, label: string) {
const button = Array.from(container.querySelectorAll('[role="tab"]')).find(
(el) => el.textContent?.trim() === label,
) as HTMLButtonElement | undefined
button?.focus()
button?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }),
)
await flushPromises()
}
describe('curl and Response tabs render, unchecked visually', () => {
it('shows the curl command and the Response JSON sample built from the real Article fields', async () => {
stubOrigin(STUB_ORIGIN)
const wrapper = await mountIntegration()
const firstCard = wrapper.findAllComponents(EndpointCard)[0]
await activateTab(firstCard.element as HTMLElement, 'curl')
expect(firstCard.text()).toContain(`curl ${STUB_ORIGIN}/v1/article/`)
await activateTab(firstCard.element as HTMLElement, 'Response')
expect(firstCard.text()).toContain('"is_online"')
expect(firstCard.text()).toContain('"created_at"')
expect(firstCard.text()).toContain('"id"')
})
})