Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
run: node scripts/check-dx-coverage.mjs

website:
name: Website — lint / build
name: Website — lint / test / build
needs: ci-scope
if: github.event_name == 'push' || needs.ci-scope.outputs.website == 'true'
runs-on: ubuntu-latest
Expand All @@ -114,6 +114,7 @@ jobs:
cache: npm
- run: npm ci
- run: npx nx lint website
- run: npx nx test website
- run: npm run generate-api-docs
- name: Commit generated API docs to same-repo PR
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
Expand Down Expand Up @@ -543,7 +544,7 @@ jobs:

require_always "CI scope" "$RESULT_CI_SCOPE"
require_scoped "library" "Library — lint / test / build" "$RESULT_LIBRARY" "$SCOPE_LIBRARY"
require_scoped "website" "Website — lint / build" "$RESULT_WEBSITE" "$SCOPE_WEBSITE"
require_scoped "website" "Website — lint / test / build" "$RESULT_WEBSITE" "$SCOPE_WEBSITE"
require_scoped "cockpit" "Cockpit — build / test" "$RESULT_COCKPIT" "$SCOPE_COCKPIT"
require_scoped "cockpit_examples" "Cockpit — build all examples" "$RESULT_COCKPIT_EXAMPLES" "$SCOPE_COCKPIT_EXAMPLES"
require_scoped "cockpit_smoke" "Cockpit — representative capability smoke" "$RESULT_COCKPIT_SMOKE" "$SCOPE_COCKPIT_SMOKE"
Expand Down
6 changes: 6 additions & 0 deletions apps/website/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
"{options.outputFile}"
]
},
"test": {
"executor": "@nx/vitest:test",
"options": {
"configFile": "apps/website/vite.config.mts"
}
},
"e2e": {
"executor": "nx:run-commands",
"options": {
Expand Down
35 changes: 27 additions & 8 deletions apps/website/src/app/thanks/page.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,40 @@ vi.mock('../../components/ui/Button', () => ({
}));

describe('ThanksPage', () => {
it('renders the payment-received heading', () => {
render(<ThanksPage />);
// ThanksPage is an async Server Component taking `searchParams: Promise<...>`.
// Rendering it as JSX synchronously yields an empty DOM — which is why every
// assertion in this file failed against `<body><div /></body>`. Await the
// component and render the element it resolves to.
const renderPage = async (searchParams: { session_id?: string } = {}) =>
render(await ThanksPage({ searchParams: Promise.resolve(searchParams) }));

it('renders the payment-received heading', async () => {
await renderPage();
expect(screen.getByRole('heading', { level: 1, name: 'Thanks for your purchase.' })).toBeTruthy();
});

it('mentions provideChat() activation', () => {
render(<ThanksPage />);
it('mentions provideChat() activation', async () => {
await renderPage();
expect(screen.getByText(/provideChat\(\)/)).toBeTruthy();
});

it('links to installation docs and contact', () => {
render(<ThanksPage />);
expect(screen.getByRole('link', { name: 'Installation docs' }).getAttribute('href'))
.toBe('/docs/chat/getting-started/installation');
it('links to licensing docs and contact', async () => {
await renderPage();
expect(screen.getByRole('link', { name: 'Installation & licensing' }).getAttribute('href'))
.toBe('/docs/licensing');
expect(screen.getByRole('link', { name: 'Contact support' }).getAttribute('href'))
.toBe('/contact');
});

it('offers the billing portal only for a well-formed Stripe session id', async () => {
const { unmount } = await renderPage({ session_id: 'cs_test_abc123' });
expect(screen.getByRole('link', { name: 'Manage subscription' }).getAttribute('href'))
.toBe('/api/portal/session?session_id=cs_test_abc123');
unmount();

// A malformed id must not produce a portal link — this is the guard that
// keeps an arbitrary query value out of the portal URL.
await renderPage({ session_id: 'not-a-session' });
expect(screen.queryByRole('link', { name: 'Manage subscription' })).toBeNull();
});
});
7 changes: 5 additions & 2 deletions apps/website/src/components/blog/PostCard.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { describe, expect, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import { PostCard } from './PostCard';
import type { Post } from '../../lib/blog';
import { formatCardDate, type Post } from '../../lib/blog';

const post: Post = {
slug: 'streaming-chat',
Expand All @@ -22,7 +22,10 @@ describe('PostCard', () => {
it('renders title, date, and tag chips', () => {
render(<PostCard post={post} />);
expect(screen.getByText('Build a streaming chat UI')).toBeTruthy();
expect(screen.getByText('2026-05-17')).toBeTruthy();
// Derive through the same formatter the card uses. `formatCardDate` omits
// the year for same-year posts, so a literal ('May 17') would silently rot
// the moment the calendar year rolls over.
expect(screen.getByText(new RegExp(formatCardDate(post.frontmatter.date)))).toBeTruthy();
expect(screen.getByText('tutorial')).toBeTruthy();
expect(screen.getByText('streaming')).toBeTruthy();
});
Expand Down
5 changes: 4 additions & 1 deletion apps/website/src/components/landing/Differentiator.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const EXPECTED_NEEDS = [
'Backend portability',
'Angular-native',
'Observability hooks',
'MIT + self-hosted',
'Open adapters + self-hosted',
];

describe('Differentiator', () => {
Expand All @@ -55,6 +55,9 @@ describe('Differentiator', () => {
for (const need of EXPECTED_NEEDS) {
expect(screen.getByText(need)).toBeTruthy();
}
// Guards silent row loss: without this, deleting a row from the component
// AND its line here would leave the suite green on a shrunken table.
expect(EXPECTED_NEEDS).toHaveLength(10);
});

it('renders the @threadplane/render primitive for the generative UI row', () => {
Expand Down
Loading