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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
playwright-report/
test-results/
# Compiled output
/dist
/tmp
Expand Down
68 changes: 64 additions & 4 deletions e2e/landing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ test.describe('Landing Page', () => {
await expect(nav.getByRole('link', { name: 'Lumen' })).toBeVisible();
});

test('has GitHub link in navigation', async ({ page }) => {
const githubLink = page.getByRole('navigation').getByRole('link', { name: 'GitHub' });
await expect(githubLink).toBeVisible();
await expect(githubLink).toHaveAttribute('href', 'https://github.com/Andersseen');
});

test('navigates to Volt UI page', async ({ page }) => {
await page.getByRole('navigation').getByRole('link', { name: 'Volt UI' }).click();
await expect(page).toHaveURL(/\/volt-ui/);
Expand Down Expand Up @@ -73,10 +79,10 @@ test.describe('404 Page', () => {
test.describe('Page Content', () => {
test('Volt UI page shows buttons', async ({ page }) => {
await page.goto('/volt-ui');
await expect(page.getByText('Primary')).toBeVisible();
await expect(page.getByText('Destructive')).toBeVisible();
await expect(page.getByText('Outline')).toBeVisible();
await expect(page.getByText('Ghost')).toBeVisible();
await expect(page.getByRole('button', { name: 'Primary' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Destructive' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Outline' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Ghost' })).toBeVisible();
});

test('Quartz page shows toast buttons', async ({ page }) => {
Expand All @@ -101,3 +107,57 @@ test.describe('Page Content', () => {
await expect(page.getByText('Settings', { exact: true })).toBeVisible();
});
});

test.describe('Demo Pages External Links', () => {
test('Volt UI page has install command, GitHub and Live Demo links', async ({ page }) => {
await page.goto('/volt-ui');
await page.waitForSelector('h1');

await expect(page.getByText('npm install @voltui/components')).toBeVisible();

const githubLink = page.locator('a[href="https://github.com/Andersseen/volt-ui"]');
await expect(githubLink).toBeVisible();

const demoLink = page.locator('a[href="https://volt-ui.andersseen.dev"]');
await expect(demoLink).toBeVisible();
});

test('Quartz page has install command, GitHub and Live Demo links', async ({ page }) => {
await page.goto('/quartz');
await page.waitForSelector('h1');

await expect(page.getByText('npm install quartz-headless')).toBeVisible();

const githubLink = page.locator('a[href="https://github.com/Andersseen/quartz"]');
await expect(githubLink).toBeVisible();

const demoLink = page.locator('a[href="https://quartz-ui.andersseen.dev"]');
await expect(demoLink).toBeVisible();
});

test('Angular Movement page has install command, GitHub and Live Demo links', async ({ page }) => {
await page.goto('/angular-movement');
await page.waitForSelector('h1');

await expect(page.getByText('npm install angular-movement')).toBeVisible();

const githubLink = page.locator('a[href="https://github.com/Andersseen/angular-movement"]');
await expect(githubLink).toBeVisible();

const demoLink = page.locator('a[href="https://angular-movement.andersseen.dev"]');
await expect(demoLink).toBeVisible();
});

test('Lumen Icons page has install command, GitHub and Live Demo links', async ({ page }) => {
await page.goto('/lumen-icons');
await page.waitForSelector('h1');

await expect(page.getByText('npm install @lumen/icons')).toBeVisible();

const githubLink = page.locator('a[href="https://github.com/Andersseen/lumen-icons"]');
await expect(githubLink).toBeVisible();

const demoLink = page.locator('a[href="https://lumen-icons.andersseen.dev"]');
await expect(demoLink).toBeVisible();
});
});
23 changes: 23 additions & 0 deletions src/app/components/demo/demo-card.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/angular';
import { DemoCardComponent } from './demo-card.component';

describe('DemoCardComponent', () => {
it('should render content', async () => {
await render('<app-demo-card><div data-testid="card-content">Card Content</div></app-demo-card>', {
imports: [DemoCardComponent],
});

expect(screen.getByTestId('card-content')).toBeInTheDocument();
});

it('should apply vt-destination-card class when isDestination is true', async () => {
const { container } = await render(
'<app-demo-card [isDestination]="true"><div>Content</div></app-demo-card>',
{ imports: [DemoCardComponent] }
);

const card = container.querySelector('volt-card');
expect(card?.classList.contains('vt-destination-card')).toBe(true);
});
});
22 changes: 22 additions & 0 deletions src/app/components/demo/demo-card.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component, input } from '@angular/core';
import { VoltCard } from '@voltui/components';
import { MOVEMENT_DIRECTIVES } from 'angular-movement';

@Component({
selector: 'app-demo-card',
imports: [VoltCard, MOVEMENT_DIRECTIVES],
template: `
<volt-card [class]="cardClass()" [move]="'fade-up'" [moveDelay]="delay()">
<ng-content />
</volt-card>
`,
})
export class DemoCardComponent {
readonly delay = input<number>(100);
readonly isDestination = input<boolean>(false);

protected cardClass(): string {
const base = 'p-6';
return this.isDestination() ? `vt-destination-card ${base}` : base;
}
}
15 changes: 15 additions & 0 deletions src/app/components/demo/demo-code-block.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/angular';
import { DemoCodeBlockComponent } from './demo-code-block.component';

describe('DemoCodeBlockComponent', () => {
it('should render code content', async () => {
await render(DemoCodeBlockComponent, {
componentInputs: {
code: 'npm install @test/lib',
},
});

expect(screen.getByText('npm install @test/lib')).toBeInTheDocument();
});
});
12 changes: 12 additions & 0 deletions src/app/components/demo/demo-code-block.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component, input } from '@angular/core';

@Component({
selector: 'app-demo-code-block',
imports: [],
template: `
<pre class="overflow-x-auto rounded-lg bg-black/40 p-4 text-sm text-white/80"><code>{{ code() }}</code></pre>
`,
})
export class DemoCodeBlockComponent {
readonly code = input.required<string>();
}
90 changes: 90 additions & 0 deletions src/app/components/demo/demo-header.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/angular';
import { DemoHeaderComponent } from './demo-header.component';
import { provideRouter } from '@angular/router';

describe('DemoHeaderComponent', () => {
it('should render title and description', async () => {
await render(DemoHeaderComponent, {
providers: [provideRouter([])],
componentInputs: {
title: 'Test Lib',
description: 'A test description',
},
});

expect(screen.getByText('Test Lib')).toBeInTheDocument();
expect(screen.getByText('A test description')).toBeInTheDocument();
});

it('should render back link', async () => {
await render(DemoHeaderComponent, {
providers: [provideRouter([])],
componentInputs: {
title: 'Test Lib',
description: 'A test description',
},
});

const backLink = screen.getByText('← Volver');
expect(backLink).toBeInTheDocument();
expect(backLink.closest('a')).toHaveAttribute('href', '/');
});

it('should render install pill when packageName is provided', async () => {
await render(DemoHeaderComponent, {
providers: [provideRouter([])],
componentInputs: {
title: 'Test Lib',
description: 'A test description',
packageName: '@test/lib',
},
});

expect(screen.getByText('npm install @test/lib')).toBeInTheDocument();
});

it('should render GitHub link when githubUrl is provided', async () => {
await render(DemoHeaderComponent, {
providers: [provideRouter([])],
componentInputs: {
title: 'Test Lib',
description: 'A test description',
githubUrl: 'https://github.com/test/lib',
},
});

const githubLink = screen.getByText('GitHub').closest('a');
expect(githubLink).toHaveAttribute('href', 'https://github.com/test/lib');
expect(githubLink).toHaveAttribute('target', '_blank');
});

it('should render Live Demo link when demoUrl is provided', async () => {
await render(DemoHeaderComponent, {
providers: [provideRouter([])],
componentInputs: {
title: 'Test Lib',
description: 'A test description',
demoUrl: 'https://demo.example.com',
},
});

const demoLink = screen.getByText('Live Demo').closest('a');
expect(demoLink).toHaveAttribute('href', 'https://demo.example.com');
expect(demoLink).toHaveAttribute('target', '_blank');
});

it('should not render optional elements when not provided', async () => {
await render(DemoHeaderComponent, {
providers: [provideRouter([])],
componentInputs: {
title: 'Test Lib',
description: 'A test description',
},
});

expect(screen.queryByText(/npm install/)).not.toBeInTheDocument();
expect(screen.queryByText('GitHub')).not.toBeInTheDocument();
expect(screen.queryByText('Live Demo')).not.toBeInTheDocument();
});
});
56 changes: 56 additions & 0 deletions src/app/components/demo/demo-header.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Component, input } from '@angular/core';
import { RouterLink } from '@angular/router';
import { MOVEMENT_DIRECTIVES } from 'angular-movement';
import { GithubIconComponent } from '../github-icon.component';

@Component({
selector: 'app-demo-header',
imports: [RouterLink, MOVEMENT_DIRECTIVES, GithubIconComponent],
template: `
<div class="mb-8" [move]="'fade-up'">
<a routerLink="/" class="text-sm text-white/50 transition-colors hover:text-white">← Volver</a>
<h1 class="mt-4 mb-2 text-4xl font-bold">{{ title() }}</h1>
<p class="text-white/60">{{ description() }}</p>

<div class="mt-6 flex flex-wrap items-center gap-3">
@if (packageName(); as pkg) {
<div
class="flex items-center gap-2 rounded-lg border border-white/10 bg-black/30 px-3 py-2 font-mono text-xs text-white/70"
>
<span class="text-white/40">$</span>
<span>npm install {{ pkg }}</span>
</div>
}
@if (githubUrl(); as url) {
<a
[href]="url"
target="_blank"
rel="noopener"
class="inline-flex items-center gap-2 rounded-lg border border-white/10 bg-white/5 px-4 py-2 text-sm text-white/70 transition hover:bg-white/10 hover:text-white"
>
<app-github-icon [size]="16" />
GitHub
</a>
}
@if (demoUrl(); as url) {
<a
[href]="url"
target="_blank"
rel="noopener"
class="inline-flex items-center gap-1 rounded-lg border border-white/10 bg-white/5 px-4 py-2 text-sm font-medium text-white/80 transition hover:bg-white/10 hover:text-white"
>
Live Demo
<span>→</span>
</a>
}
</div>
</div>
`,
})
export class DemoHeaderComponent {
readonly title = input.required<string>();
readonly description = input.required<string>();
readonly packageName = input<string>();
readonly githubUrl = input<string>();
readonly demoUrl = input<string>();
}
13 changes: 13 additions & 0 deletions src/app/components/demo/demo-layout.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/angular';
import { DemoLayoutComponent } from './demo-layout.component';

describe('DemoLayoutComponent', () => {
it('should render content', async () => {
await render('<app-demo-layout accentRgb="255 0 0"><div data-testid="content">Hello</div></app-demo-layout>', {
imports: [DemoLayoutComponent],
});

expect(screen.getByTestId('content')).toBeInTheDocument();
});
});
22 changes: 22 additions & 0 deletions src/app/components/demo/demo-layout.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component, input } from '@angular/core';

@Component({
selector: 'app-demo-layout',
imports: [],
template: `
<div class="relative min-h-screen overflow-hidden">
<div
class="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_80%_60%_at_50%_0%,rgba(var(--accent-rgb),0.08),transparent)]"
></div>
<div class="relative z-10 mx-auto max-w-3xl px-6 py-12">
<ng-content />
</div>
</div>
`,
host: {
'[style.--accent-rgb]': 'accentRgb()',
},
})
export class DemoLayoutComponent {
readonly accentRgb = input<string>('59 130 246');
}
13 changes: 13 additions & 0 deletions src/app/components/demo/demo-section.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/angular';
import { DemoSectionComponent } from './demo-section.component';

describe('DemoSectionComponent', () => {
it('should render content inside section', async () => {
await render('<app-demo-section><div data-testid="section-content">Section Content</div></app-demo-section>', {
imports: [DemoSectionComponent],
});

expect(screen.getByTestId('section-content')).toBeInTheDocument();
});
});
12 changes: 12 additions & 0 deletions src/app/components/demo/demo-section.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-demo-section',
imports: [],
template: `
<section class="space-y-8">
<ng-content />
</section>
`,
})
export class DemoSectionComponent {}
5 changes: 5 additions & 0 deletions src/app/components/demo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { DemoLayoutComponent } from './demo-layout.component';
export { DemoHeaderComponent } from './demo-header.component';
export { DemoCardComponent } from './demo-card.component';
export { DemoCodeBlockComponent } from './demo-code-block.component';
export { DemoSectionComponent } from './demo-section.component';
Loading
Loading