// @vitest-environment happy-dom import { describe, it, expect } from 'vitest' import SourceHealthDot from '../../src/components/subscriptions/SourceHealthDot.vue' import { VTooltipStub, mountComponent } from '../support/mountComponent.js' // Milestone #387 A3. The dot is the only always-visible signal per source, so // the grade it picks IS the claim FC makes about that subscription. These pin // that no-access is graded as its own thing — not as healthy (which hides it) // and not as a failure (which would send the operator hunting for a break that // isn't there). const checked = { last_checked_at: '2026-09-09T12:00:00+00:00' } function dotClass (w) { return w.find('.fc-health-dot').classes().join(' ') } describe('SourceHealthDot', () => { it('grades a tier-gated source as no-access, not healthy', () => { const w = mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { ...checked, consecutive_failures: 0, error_type: 'tier_limited' }, }, }) expect(dotClass(w)).toContain('fc-health-dot--no-access') expect(dotClass(w)).not.toContain('fc-health-dot--healthy') }) it('shows the gated count when the list endpoint supplied one', () => { const w = mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { ...checked, consecutive_failures: 0, error_type: 'tier_limited', tier_gated_count: 47, }, }, }) expect(w.text()).toContain('47 posts') }) it('states the condition without a number when no count was joined in', () => { const w = mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { ...checked, consecutive_failures: 0, error_type: 'tier_limited', tier_gated_count: null, }, }, }) // Absent is not zero: never render "0 posts you don't have access to". expect(w.text()).not.toContain('0 post') expect(w.text()).toContain("tier you don't hold") }) // Milestone #387 C5. The count says WHAT; these say WHY — a much stronger // claim, so the cases that must stay silent are pinned alongside the ones // that speak. function gated (extra) { return mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { ...checked, consecutive_failures: 0, error_type: 'tier_limited', tier_gated_count: 47, ...extra, }, }, }) } it('a lapsed membership says the subscription ended', () => { expect(gated({ gated_reason: 'lapsed' }).text()).toContain('not a patron any more') }) it('an active membership says the tier is the limit, not the subscription', () => { const text = gated({ gated_reason: 'tier' }).text() expect(text).toContain("tier doesn't cover these posts") expect(text).not.toContain('not a patron any more') }) it('a free follow is not described as a lapsed subscription', () => { const text = gated({ gated_reason: 'free' }).text() expect(text).toContain('follow this creator for free') expect(text).not.toContain('not a patron any more') }) it('no reason means the count stands alone, with no invented explanation', () => { // null covers all four not-evidence cases the backend collapses into it: // campaign absent, roster stale, never swept, status uncharacterised. const text = gated({ gated_reason: null }).text() expect(text).toContain('47 posts') expect(text).not.toContain('patron') expect(text).not.toContain('tier doesn') }) it('a reason the frontend has not been taught renders nothing', () => { // The backend's status vocabulary grows per platform (D1). An unknown word // must degrade to the bare count, never to `undefined` in the tooltip. const text = gated({ gated_reason: 'some_future_word' }).text() expect(text).toContain('47 posts') expect(text).not.toContain('undefined') }) it('a reason never appears on a source that is not gated', () => { const w = mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { ...checked, consecutive_failures: 0, error_type: null, gated_reason: 'lapsed', }, }, }) // The roster annotates a gated state; it never asserts one on its own. expect(w.text()).not.toContain('not a patron any more') }) it('a genuinely failing source still grades as a failure, gated or not', () => { const w = mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { ...checked, consecutive_failures: 9, error_type: 'tier_limited' }, warningThreshold: 5, }, }) expect(dotClass(w)).toContain('fc-health-dot--critical') expect(dotClass(w)).not.toContain('fc-health-dot--no-access') }) it('an unchecked source is still unchecked', () => { const w = mountComponent(SourceHealthDot, { stubs: { VTooltip: VTooltipStub }, props: { source: { last_checked_at: null, consecutive_failures: 0 } }, }) expect(dotClass(w)).toContain('fc-health-dot--unchecked') }) })