-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement Bloqr landing page — 10 section components, dark design system, SSR-safe persona tabs, a11y hardening #1582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
35878e9
Initial plan
Copilot ede2bb8
Initial plan
Copilot 45005e2
feat: redesign frontend and API with Bloqr brand design system
Copilot dab1793
fix: improve font preload comment and CDN exception documentation
Copilot dd10c88
feat: implement Bloqr landing page with 10 section components
Copilot 4be077f
fix: address code review - accurate isLandingPage initial value, SSR-…
Copilot fc2e8b0
merge: resolve conflicts with copilot/redesign-angular-frontend-and-a…
Copilot 314f04b
merge: resolve conflicts with main (isLandingPage guard, font preload…
Copilot 47e59d4
Update frontend/src/app/app.component.ts
jaypatrick a653f90
fix: address PR review feedback — bundle logo, fix a11y, replace hard…
Copilot 1cbb9dc
fix: use boolean true for inert binding and CSS var for orange-glow-i…
Copilot 889460c
fix: apply second review feedback — label semantics, test stub, jose pin
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,141 +1,51 @@ | ||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
| import { provideZonelessChangeDetection } from '@angular/core'; | ||
| import { provideRouter, Router } from '@angular/router'; | ||
| import { provideHttpClient } from '@angular/common/http'; | ||
| import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing'; | ||
| import { NoopAnimationsModule } from '@angular/platform-browser/animations'; | ||
| import { provideRouter } from '@angular/router'; | ||
| import { HomeComponent } from './home.component'; | ||
| import { API_BASE_URL } from '../tokens'; | ||
|
|
||
| describe('HomeComponent', () => { | ||
| let fixture: ComponentFixture<HomeComponent>; | ||
| let component: HomeComponent; | ||
| let httpTesting: HttpTestingController; | ||
| let router: Router; | ||
|
|
||
| beforeEach(async () => { | ||
| await TestBed.configureTestingModule({ | ||
| imports: [HomeComponent, NoopAnimationsModule], | ||
| imports: [HomeComponent], | ||
| providers: [ | ||
| provideZonelessChangeDetection(), | ||
| provideHttpClient(), | ||
| provideHttpClientTesting(), | ||
| provideRouter([]), | ||
| { provide: API_BASE_URL, useValue: '/api' }, | ||
| ], | ||
| }).compileComponents(); | ||
|
|
||
| fixture = TestBed.createComponent(HomeComponent); | ||
| component = fixture.componentInstance; | ||
| httpTesting = TestBed.inject(HttpTestingController); | ||
| router = TestBed.inject(Router); | ||
|
|
||
| // Flush the initial rxResource requests triggered by component creation | ||
| flushPendingRequests(); | ||
| }); | ||
|
|
||
| function flushPendingRequests(): void { | ||
| httpTesting.match('/api/metrics').forEach(req => req.flush({ | ||
| totalRequests: 0, averageDuration: 0, cacheHitRate: 0, successRate: 0, | ||
| })); | ||
| httpTesting.match('/api/health').forEach(req => req.flush({ | ||
| status: 'healthy', version: '0.0.0', | ||
| })); | ||
| } | ||
|
|
||
| afterEach(() => { | ||
| httpTesting.match(() => true).forEach(req => req.flush({})); | ||
| httpTesting.verify(); | ||
| vi.restoreAllMocks(); | ||
| await fixture.whenStable(); | ||
| }); | ||
|
|
||
| it('should create', () => { | ||
| expect(component).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('should have 6 navigation cards', () => { | ||
| expect(component.navCards.length).toBe(6); | ||
| }); | ||
|
|
||
| it('should include Compiler card', () => { | ||
| const compiler = component.navCards.find(c => c.path === '/compiler'); | ||
| expect(compiler).toBeTruthy(); | ||
| expect(compiler!.title).toBe('Filter List Compiler'); | ||
| }); | ||
|
|
||
| it('should include Admin card with warn tag', () => { | ||
| const admin = component.navCards.find(c => c.path === '/admin'); | ||
| expect(admin).toBeTruthy(); | ||
| expect(admin!.tagColor).toBe('warn'); | ||
| }); | ||
|
|
||
| it('should derive live stats from metrics', () => { | ||
| // After flushing with zeroed metrics, stats show formatted values | ||
| const stats = component.liveStats(); | ||
| expect(stats.length).toBe(5); | ||
| expect(stats[0].label).toBe('Total Requests'); | ||
| expect(stats[0].value).toBe('0'); | ||
| }); | ||
|
|
||
| it('should navigate when navigateTo is called', () => { | ||
| const navigateSpy = vi.spyOn(router, 'navigate'); | ||
| component.navigateTo('/compiler'); | ||
| expect(navigateSpy).toHaveBeenCalledWith(['/compiler']); | ||
| }); | ||
|
|
||
| it('should open absolute external URLs in a new tab via window.open', () => { | ||
| const openSpy = vi.spyOn(window, 'open'); | ||
| component.navigateTo('https://example.com'); | ||
| expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank', 'noopener,noreferrer'); | ||
| }); | ||
|
|
||
| it('should open worker-handled relative paths in a new tab when external flag is true', () => { | ||
| const openSpy = vi.spyOn(window, 'open'); | ||
| const navigateSpy = vi.spyOn(router, 'navigate'); | ||
| component.navigateTo('/docs', true); | ||
| expect(openSpy).toHaveBeenCalledWith('/docs', '_blank', 'noopener,noreferrer'); | ||
| expect(navigateSpy).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should NOT call window.open for internal paths without external flag', () => { | ||
| const openSpy = vi.spyOn(window, 'open'); | ||
| const navigateSpy = vi.spyOn(router, 'navigate'); | ||
| component.navigateTo('/compiler', false); | ||
| expect(openSpy).not.toHaveBeenCalled(); | ||
| expect(navigateSpy).toHaveBeenCalledWith(['/compiler']); | ||
| }); | ||
|
|
||
| it('should navigate to performance on stat card click for Total Requests', () => { | ||
| const navigateSpy = vi.spyOn(router, 'navigate'); | ||
| component.onStatCardClicked('Total Requests'); | ||
| expect(navigateSpy).toHaveBeenCalledWith(['/performance']); | ||
| }); | ||
|
|
||
| it('should navigate to performance on stat card click for Avg Response Time', () => { | ||
| const navigateSpy = vi.spyOn(router, 'navigate'); | ||
| component.onStatCardClicked('Avg Response Time'); | ||
| expect(navigateSpy).toHaveBeenCalledWith(['/performance']); | ||
| }); | ||
|
|
||
| it('should not navigate for non-metric stat card clicks', () => { | ||
| const navigateSpy = vi.spyOn(router, 'navigate'); | ||
| component.onStatCardClicked('Cache Hit Rate'); | ||
| expect(navigateSpy).not.toHaveBeenCalled(); | ||
| it('should render nav bar', async () => { | ||
| await fixture.whenStable(); | ||
| const nav = fixture.nativeElement.querySelector('app-nav-bar'); | ||
| expect(nav).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('should show default health icon when no data', () => { | ||
| // After flushing with healthy status, icon reflects healthy state | ||
| expect(component.healthIcon()).toBe('check_circle'); | ||
| it('should render hero section', async () => { | ||
| await fixture.whenStable(); | ||
| const hero = fixture.nativeElement.querySelector('app-hero-section'); | ||
| expect(hero).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('should show default health color when no data', () => { | ||
| // After flushing with healthy status, color reflects healthy state | ||
| expect(component.healthColor()).toBe('var(--app-success, #4caf50)'); | ||
| it('should render landing content wrapper', async () => { | ||
| await fixture.whenStable(); | ||
| const wrapper = fixture.nativeElement.querySelector('.landing-content'); | ||
| expect(wrapper).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('should render the page heading', () => { | ||
| fixture.detectChanges(); | ||
| const el: HTMLElement = fixture.nativeElement; | ||
| expect(el.querySelector('h1')?.textContent).toContain('Adblock Compiler Dashboard'); | ||
| it('should render footer section', async () => { | ||
| await fixture.whenStable(); | ||
| const footer = fixture.nativeElement.querySelector('app-footer-section'); | ||
| expect(footer).toBeTruthy(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.