Skip to content

Commit e5d382c

Browse files
Add cookie consent helper and update tests to handle cookie consent functionality
1 parent fec9cdf commit e5d382c

File tree

5 files changed

+232
-19
lines changed

5 files changed

+232
-19
lines changed

e2e/example.spec.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
import { expect, test } from '@playwright/test';
1+
import { test, expect } from '@playwright/test';
2+
import { acceptCookies } from './helpers/cookie-consent.helper';
23

3-
test('has title and handles cookie consent', async ({ page }) => {
4+
test('has title', async ({ page }) => {
45
await page.goto('/');
5-
await expect(page).toHaveTitle(/AngularBlogApp/);
6-
7-
const cookieConsentDialog = page
8-
.getByLabel('Cookie Consent')
9-
.locator('div')
10-
.filter({ hasText: 'Cookie Consent Consent' })
11-
.nth(1);
126

13-
await expect(cookieConsentDialog).toBeVisible();
7+
// Handle cookie consent using helper
8+
await acceptCookies(page);
149

15-
const allowAllButton = page.getByRole('button', { name: 'Allow All' });
16-
await allowAllButton.click();
17-
18-
await expect(cookieConsentDialog).not.toBeVisible();
10+
// Expect a title "to contain" a substring.
11+
await expect(page).toHaveTitle(/AngularBlogApp/);
1912
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Page, expect } from '@playwright/test';
2+
3+
export async function acceptCookies(page: Page): Promise<void> {
4+
const cookieConsentDialog = page
5+
.getByLabel('Cookie Consent')
6+
.locator('div')
7+
.filter({ hasText: 'Cookie Consent Consent' })
8+
.nth(1);
9+
10+
await expect(cookieConsentDialog).toBeVisible();
11+
12+
const allowAllButton = page.getByRole('button', { name: 'Allow All' });
13+
await allowAllButton.click();
14+
15+
await expect(cookieConsentDialog).not.toBeVisible();
16+
}

e2e/tags.spec.ts

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { test, expect } from '@playwright/test';
2+
import { acceptCookies } from './helpers/cookie-consent.helper';
3+
4+
test.describe('Tags Display and API', () => {
5+
test('should display tags and verify API call', async ({ page }) => {
6+
// Set up network monitoring before navigation
7+
const apiRequests: Array<{ url: string; method: string; headers: Record<string, string> }> = [];
8+
9+
page.on('request', (request) => {
10+
const url = request.url();
11+
if (url.includes('/rest/v1/tags')) {
12+
apiRequests.push({
13+
url,
14+
method: request.method(),
15+
headers: request.headers()
16+
});
17+
}
18+
});
19+
20+
// Navigate to homepage
21+
await page.goto('/');
22+
23+
// Handle cookie consent
24+
await acceptCookies(page);
25+
26+
// Wait for tags container to load
27+
await page.waitForSelector('[data-testid="tags-container"]', { timeout: 15000 });
28+
29+
// Verify tags container is visible
30+
const tagsContainer = page.locator('[data-testid="tags-container"]');
31+
await expect(tagsContainer).toBeVisible();
32+
33+
// Verify scroll container
34+
const scrollContainer = page.locator('[data-testid="tags-scroll-container"]');
35+
await expect(scrollContainer).toBeVisible();
36+
37+
// Wait for at least one tag to appear
38+
await page.waitForSelector('[data-testid="tag-item"]', { timeout: 10000 });
39+
40+
// Verify specific tags are displayed with correct structure
41+
const expectedTags = [
42+
{ name: 'Angular', color: '#DD0031', icon: 'angular.svg' },
43+
{ name: 'TypeScript', color: '#007ACC', icon: 'typescript.svg' },
44+
{ name: 'JavaScript', color: '#F7DF1E', icon: 'javascript.svg' },
45+
{ name: 'Firebase', color: '#FFCA28', icon: 'firebase.svg' },
46+
{ name: 'Node.js', color: '#339933', icon: 'nodejs.svg' }
47+
];
48+
49+
for (const tag of expectedTags) {
50+
// Find tag by data attribute
51+
const tagItem = page.locator(`[data-testid="tag-item"][data-tag-name="${tag.name}"]`);
52+
await expect(tagItem).toBeVisible();
53+
54+
// Check tag name
55+
const tagName = tagItem.locator('[data-testid="tag-name"]');
56+
await expect(tagName).toHaveText(tag.name);
57+
58+
// Check tag icon
59+
const tagIcon = tagItem.locator('[data-testid="tag-icon"]');
60+
await expect(tagIcon).toBeVisible();
61+
await expect(tagIcon).toHaveAttribute('alt', tag.name);
62+
await expect(tagIcon).toHaveAttribute('src', expect.stringContaining(tag.icon));
63+
64+
// Check background color (the parent div with inline style)
65+
const tagButton = tagItem.locator('[data-testid="tag-button"]');
66+
await expect(tagButton).toHaveAttribute('style', expect.stringContaining(tag.color));
67+
}
68+
69+
// Verify navigation buttons (desktop only)
70+
const leftButton = page.locator('[data-testid="scroll-left-button"]');
71+
const rightButton = page.locator('[data-testid="scroll-right-button"]');
72+
73+
// Check if buttons exist (they might be hidden on mobile)
74+
await expect(leftButton).toBeAttached();
75+
await expect(rightButton).toBeAttached();
76+
77+
78+
// Test tag hover effects (desktop)
79+
const viewport = page.viewportSize();
80+
if (viewport && viewport.width >= 768) {
81+
const firstTagButton = page.locator('[data-testid="tag-button"]').first();
82+
await firstTagButton.hover();
83+
84+
// Should have hover classes
85+
await expect(firstTagButton).toHaveClass(/hover:scale-110/);
86+
}
87+
88+
// Verify API call was made
89+
await page.waitForTimeout(2000); // Give time for API calls to complete
90+
expect(apiRequests.length).toBeGreaterThan(0);
91+
92+
const tagsApiCall = apiRequests[0];
93+
expect(tagsApiCall.url).toContain('/rest/v1/tags');
94+
expect(tagsApiCall.method).toBe('GET');
95+
96+
// Verify headers
97+
expect(tagsApiCall.headers['apikey']).toBeDefined();
98+
expect(tagsApiCall.headers['authorization']).toContain('Bearer');
99+
expect(tagsApiCall.headers['accept']).toBe('application/json');
100+
101+
console.log('Tags API call verified:', tagsApiCall);
102+
});
103+
104+
test('should handle tag interactions and scrolling', async ({ page }) => {
105+
await page.goto('/');
106+
await acceptCookies(page);
107+
108+
// Wait for tags to load
109+
await page.waitForSelector('[data-testid="tags-container"]', { timeout: 15000 });
110+
await page.waitForSelector('[data-testid="tag-item"]', { timeout: 10000 });
111+
112+
// Test clicking on a tag (verify it's clickable)
113+
const angularTag = page.locator('[data-testid="tag-item"][data-tag-name="Angular"]');
114+
await expect(angularTag).toBeVisible();
115+
116+
// Verify cursor pointer styling on tag button
117+
const tagButton = angularTag.locator('[data-testid="tag-button"]');
118+
await expect(tagButton).toHaveClass(/cursor-pointer/);
119+
120+
// Test scroll functionality
121+
const scrollContainer = page.locator('[data-testid="tags-scroll-container"]');
122+
123+
// Get initial scroll position
124+
const initialScrollLeft = await scrollContainer.evaluate(el => el.scrollLeft);
125+
126+
// Test desktop scroll buttons if visible
127+
const viewport = page.viewportSize();
128+
if (viewport && viewport.width >= 768) {
129+
const rightButton = page.locator('[data-testid="scroll-right-button"]');
130+
if (await rightButton.isVisible()) {
131+
await rightButton.click();
132+
133+
// Verify scroll position changed
134+
await page.waitForTimeout(500); // Wait for scroll animation
135+
const newScrollLeft = await scrollContainer.evaluate(el => el.scrollLeft);
136+
expect(newScrollLeft).toBeGreaterThan(initialScrollLeft);
137+
}
138+
} else {
139+
// Test mobile scroll
140+
await scrollContainer.evaluate(el => el.scrollLeft += 100);
141+
142+
// Verify scroll position changed
143+
const newScrollLeft = await scrollContainer.evaluate(el => el.scrollLeft);
144+
expect(newScrollLeft).toBeGreaterThan(initialScrollLeft);
145+
}
146+
});
147+
148+
test('should display all expected tags from seed data', async ({ page }) => {
149+
await page.goto('/');
150+
await acceptCookies(page);
151+
152+
await page.waitForSelector('[data-testid="tags-container"]', { timeout: 15000 });
153+
await page.waitForSelector('[data-testid="tag-item"]', { timeout: 10000 });
154+
155+
// All tags from seed data
156+
const allExpectedTags = [
157+
'Angular', 'TypeScript', 'JavaScript', 'Firebase', 'Firestore',
158+
'Node.js', 'Cloud Computing', 'SSG/SSR', 'Web Development',
159+
'Performance', 'Security', 'Deployment', 'Testing', 'Best Practices',
160+
'Tutorials', 'HTML', 'CSS'
161+
];
162+
163+
// Count visible tags using data-testid
164+
const tagElements = page.locator('[data-testid="tag-item"]');
165+
const tagCount = await tagElements.count();
166+
167+
console.log(`Found ${tagCount} tags on the page`);
168+
expect(tagCount).toBeGreaterThanOrEqual(5); // At least some tags should be visible
169+
170+
// Check for specific important tags
171+
const importantTags = ['Angular', 'TypeScript', 'JavaScript'];
172+
for (const tagName of importantTags) {
173+
const tagElement = page.locator(`[data-testid="tag-item"][data-tag-name="${tagName}"]`);
174+
await expect(tagElement).toBeVisible();
175+
176+
const tagNameElement = tagElement.locator('[data-testid="tag-name"]');
177+
await expect(tagNameElement).toHaveText(tagName);
178+
}
179+
});
180+
181+
test('should handle empty state gracefully', async ({ page }) => {
182+
// This test could simulate no tags loaded scenario
183+
await page.goto('/');
184+
await acceptCookies(page);
185+
186+
// Wait for tags container
187+
await page.waitForSelector('[data-testid="tags-container"]', { timeout: 15000 });
188+
189+
// The container should still be visible even if no tags are loaded
190+
const tagsContainer = page.locator('[data-testid="tags-container"]');
191+
await expect(tagsContainer).toBeVisible();
192+
193+
const scrollContainer = page.locator('[data-testid="tags-scroll-container"]');
194+
await expect(scrollContainer).toBeVisible();
195+
});
196+
});

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"build:stats": "ng build --stats-json",
1212
"analyze": "webpack-bundle-analyzer dist/angular-blog-app/stats.json",
1313
"start:local": "ng serve --configuration=local",
14+
"start:local:docker": "open -a Docker",
15+
"start:local:backend": "npx supabase start",
1416
"schema:pull": "find supabase/migrations -name '*_remote_schema.sql' -delete && supabase db pull --db-url $PG_EXPORT_URL",
1517
"db:createSeed": "scripts/create-seed.sh",
1618
"db:seed": "npx @snaplet/seed init",

src/app/reader/_components/main-page/posts-list/posts-list.component.html

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,51 @@
11
<app-about-me></app-about-me>
2-
<div class="relative mt-4 pb-4">
2+
<div class="relative mt-4 pb-4" data-testid="tags-container">
33
<div
44
#scrollContainer
5+
data-testid="tags-scroll-container"
56
class="shadow-lg bg-white p-4 sm:p-6 sm:px-14 rounded-[32px] sm:rounded-[64px] overflow-x-scroll hide-scrollbar whitespace-nowrap scroll-smooth"
67
>
7-
<div class="inline-flex space-x-4 gap-x-0 md:gap-x-6">
8+
<div class="inline-flex space-x-4 gap-x-0 md:gap-x-6" data-testid="tags-list">
89
@for (tag of tags(); track tag.id) {
9-
<div class="flex flex-col items-center min-w-24 group">
10+
<div class="flex flex-col items-center min-w-24 group" data-testid="tag-item" [attr.data-tag-name]="tag.name">
1011
<div
1112
[ngStyle]="{ 'background-color': tag.color }"
1213
class="font-bold text-gray-700 rounded-full flex items-center justify-center font-mono p-3 md:p-5 min-w-18 transform transition-all duration-300 hover:scale-110 hover:shadow-lg relative cursor-pointer"
14+
data-testid="tag-button"
1315
>
1416
<img
1517
ngSrc="tags/{{ tag.icon }}"
1618
[alt]="tag.name"
1719
class="w-8 h-8 sm:w-10 sm:h-10 transition-transform duration-300 group-hover:scale-110"
1820
width="32"
1921
height="32"
22+
data-testid="tag-icon"
2023
/>
2124
</div>
2225
<span
2326
class="mt-1 sm:mt-2 text-center text-gray-600 text-sm sm:text-base"
27+
data-testid="tag-name"
2428
>{{ tag.name }}</span
2529
>
2630
</div>
2731
}
2832
</div>
2933
</div>
3034

31-
<div class="relative -top-4 mt-2 md:hidden w-full flex justify-center">
35+
<div class="relative -top-4 mt-2 md:hidden w-full flex justify-center" data-testid="mobile-progress-container">
3236
<div class="w-10/12 bg-tertiary h-1 rounded-full">
3337
<div
3438
class="bg-primary h-1 rounded-full transition-all duration-300"
3539
[style.width.%]="scrollProgress()"
40+
data-testid="mobile-progress-bar"
3641
></div>
3742
</div>
3843
</div>
3944

40-
4145
<button
4246
(click)="scrollContainer.scrollLeft = scrollContainer.scrollLeft - 135"
4347
class="btn btn-circle rounded-none absolute top-0 left-0 h-40 bg-base-100 bg-opacity-70 hover:bg-base-200 hidden md:flex rounded-tl-full rounded-bl-full"
48+
data-testid="scroll-left-button"
4449
>
4550
<svg
4651
xmlns="http://www.w3.org/2000/svg"
@@ -61,6 +66,7 @@
6166
<button
6267
(click)="scrollContainer.scrollLeft = scrollContainer.scrollLeft + 135"
6368
class="btn btn-circle rounded-none absolute top-0 right-0 h-40 bg-base-100 bg-opacity-70 hover:bg-base-200 hidden md:flex rounded-tr-full rounded-br-full"
69+
data-testid="scroll-right-button"
6470
>
6571
<svg
6672
xmlns="http://www.w3.org/2000/svg"

0 commit comments

Comments
 (0)