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
2 changes: 2 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"src/favicon.ico",
"src/ads.txt",
"src/robots.txt",
"src/llms.txt",
"src/llms-full.txt",
"src/sitemap.xml",
"src/assets",
"src/manifest.webmanifest",
Expand Down
65 changes: 65 additions & 0 deletions e2e/seo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { expect, test } from '@playwright/test';

test('home page has indexable title, description, and canonical', async ({
page,
}) => {
await page.goto('/');
await expect(page.locator('app-root')).toBeVisible({ timeout: 15000 });
await expect(page).toHaveTitle(/Ditectrev/i);
await expect(page.locator('meta[name="description"]').first()).toHaveAttribute(
'content',
/online education/i
);
await expect(page.locator('link[rel="canonical"]').first()).toHaveAttribute(
'href',
/https:\/\/ditectrev\.com\/?$/
);
});

test('about-us page uses a unique title and self-canonical', async ({
page,
}) => {
await page.goto('/about-us');
await expect(page.locator('app-root')).toBeVisible({ timeout: 15000 });
await expect(page).toHaveTitle(/About Us/i);
await expect(page.locator('link[rel="canonical"]').first()).toHaveAttribute(
'href',
/\/about-us$/
);
});

test('serves llms.txt for AI agents', async ({ request }) => {
const response = await request.get('/llms.txt');
expect(response.ok()).toBeTruthy();
const body = await response.text();
expect(body).toMatch(/^# Ditectrev/m);
expect(body).toContain('https://ditectrev.com/faq');
expect(body).toContain('llms-full.txt');
});

test('serves llms-full.txt with citation Q&A', async ({ request }) => {
const response = await request.get('/llms-full.txt');
expect(response.ok()).toBeTruthy();
const body = await response.text();
expect(body).toContain('What is Ditectrev?');
expect(body).toContain('IBStructure Daniel Danielecki');
});

test('XML sitemap lists llms.txt for crawler discovery', async ({ request }) => {
const response = await request.get('/sitemap.xml');
expect(response.ok()).toBeTruthy();
const body = await response.text();
expect(body).toContain('https://ditectrev.com/llms.txt');
expect(body).toContain('https://ditectrev.com/llms-full.txt');
});

test('unknown routes are marked noindex', async ({ page }) => {
await page.goto('/this-page-does-not-exist');
await expect(page.locator('app-root')).toBeVisible({ timeout: 15000 });
await expect(page).toHaveTitle(/Page Not Found/i);
await expect(page.locator('meta[name="robots"]').first()).toHaveAttribute(
'content',
/noindex/i
);
await expect(page.getByRole('link', { name: /homepage/i })).toBeVisible();
});
4 changes: 3 additions & 1 deletion src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'hammerjs';
import { AppComponent } from './app.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Meta, Title } from '@angular/platform-browser';
import { HomeModule } from './home/home.module';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
Expand All @@ -18,7 +19,8 @@ describe('AppComponent', () => {
HomeModule,
HttpClientTestingModule,
RouterTestingModule,
]
],
providers: [Title, Meta]
}).compileComponents();
}));

Expand Down
21 changes: 19 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { animate, style, transition, trigger } from '@angular/animations';
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
import { filter } from 'rxjs/operators';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';
import { SeoService } from './services/seo.service';

// TODO: Outsource this to constants.
const FADE_ANIMATION =
Expand All @@ -26,6 +29,20 @@ const FADE_ANIMATION =
// TODO: Add unit tests coverage for these methods.
// TODO: Uncomment ngx-spinner and fix it on Angular Universal, because now it's breaking.
export class AppComponent {
private readonly router = inject(Router);
private readonly seoService = inject(SeoService);

constructor() {
this.router.events
.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
takeUntilDestroyed()
)
.subscribe((event) => {
this.seoService.updateForUrl(event.urlAfterRedirects);
});
}

public getRouterOutletState(routerOutlet: RouterOutlet): string {
const routeData = routerOutlet.activatedRouteData['animation'];
return routeData ? routeData : 'rootPage';
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/about-us/about-us.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
role="region"
>
<!-- Title of the card. -->
<mat-card-title aria-level="1" role="heading" title="About us"
>About us</mat-card-title
<mat-card-title title="About us"
><h1>About us</h1></mat-card-title
>
<mat-card-subtitle aria-level="3"
<mat-card-subtitle
>Your Digital Transformation and Innovation Partner. Creativity and
Privacy-Preserving Approach describes us best. #cyber Security, #digital
Strategy and #software Development.</mat-card-subtitle
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/contact/contact.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
role="region"
>
<!-- Title of the card. -->
<mat-card-title aria-level="1" role="heading" title="Contact">
Contact</mat-card-title
<mat-card-title title="Contact">
<h1>Contact</h1></mat-card-title
>
<mat-card-subtitle aria-level="3"
<mat-card-subtitle
>We are Partners, not contractors. How can we help You?</mat-card-subtitle
>
<!-- Actual content starts from here. -->
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/copyrights/copyrights.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<!-- Card container that binds all together. -->
<mat-card aria-label="These are our copyrights details." class="mat-elevation-z24" role="region">
<!-- Title of the card. -->
<mat-card-title aria-level="1" title="Copyrights" role="heading">Copyrights</mat-card-title>
<mat-card-subtitle aria-level="3" role="heading">Before you copy anything from us kindly please
<mat-card-title title="Copyrights"><h1>Copyrights</h1></mat-card-title>
<mat-card-subtitle>Before you copy anything from us kindly please
read it
carefully.</mat-card-subtitle>
<!-- Actual content starts from here. -->
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/faq/faq.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<!-- Card container that binds all together. -->
<mat-card aria-label="These are Frequently Asked Questions grouped on categories." class="mat-elevation-z24" role="region">
<!-- Title of the card. -->
<mat-card-title aria-level="1" role="heading" title="FAQ">FAQ</mat-card-title>
<mat-card-subtitle aria-level="3" role="heading">Below you can find basic questions regarding cooperations, our
<mat-card-title title="FAQ"><h1>FAQ</h1></mat-card-title>
<mat-card-subtitle>Below you can find basic questions regarding cooperations, our
company
and services we provide. If you still cannot find an answer on your question do not
hesitate to contact us.</mat-card-subtitle>
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/footer/footer.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
<!-- Ditectrev's logo. -->
<a routerLink="/">
<img
alt="This is Ditectrev's logo"
alt="Ditectrev logo"
loading="lazy"
src="assets/logo.svg"
title="Ditectrev"
/>
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/glossary/glossary.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<!-- Card container that binds all together. -->
<mat-card aria-label="These are glossary details." class="mat-elevation-z24" role="region">
<!-- Title of the card. -->
<mat-card-title aria-level="1" role="heading" title="Glossary">Glossary</mat-card-title>
<mat-card-subtitle aria-level="3" role="heading">Please use the tables of terms with sorting and filtration in order
<mat-card-title title="Glossary"><h1>Glossary</h1></mat-card-title>
<mat-card-subtitle>Please use the tables of terms with sorting and filtration in order
to
find what is unclear for you.</mat-card-subtitle>

Expand Down
2 changes: 1 addition & 1 deletion src/app/components/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<!-- Ditectrev's logo. -->
<a routerLink="/">
<img
alt="This is Ditectrev's logo"
alt="Ditectrev logo"
src="assets/logo.svg"
title="Ditectrev"
/>
Expand Down
9 changes: 6 additions & 3 deletions src/app/components/home/gallery/gallery.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ <h2>What can we do for You?</h2>
<div class="gallery-main">
<img
[src]="images[currentIndex].src"
[alt]="images[currentIndex].title"
[alt]="images[currentIndex].description"
class="gallery-image"
decoding="async"
[@slideAnimation]="currentIndex"
/>
<div class="gallery-overlay">
<h3>{{ images[currentIndex].title }}</h3>
<p>{{ images[currentIndex].description }}</p>
<a [href]="images[currentIndex].link" class="gallery-link">Learn More</a>
<a [routerLink]="images[currentIndex].link" class="gallery-link">Learn more about {{ images[currentIndex].title }}</a>
</div>
<button class="gallery-nav gallery-prev" (click)="previous()" aria-label="Previous image">‹</button>
<button class="gallery-nav gallery-next" (click)="next()" aria-label="Next image">›</button>
Expand All @@ -24,9 +25,11 @@ <h3>{{ images[currentIndex].title }}</h3>
<img
*ngFor="let image of images; let i = index"
[src]="image.src"
[alt]="image.title"
[alt]="image.description"
class="thumbnail"
[class.active]="i === currentIndex"
decoding="async"
loading="lazy"
(click)="goTo(i)"
/>
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/home/gallery/gallery.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ComponentFixture, TestBed, fakeAsync, tick, discardPeriodicTasks } from "@angular/core/testing";
import { GalleryComponent } from "./gallery.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterTestingModule } from "@angular/router/testing";

describe("GalleryComponent", () => {
let component: GalleryComponent;
let fixture: ComponentFixture<GalleryComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GalleryComponent, BrowserAnimationsModule],
imports: [GalleryComponent, BrowserAnimationsModule, RouterTestingModule],
}).compileComponents();

fixture = TestBed.createComponent(GalleryComponent);
Expand Down Expand Up @@ -191,7 +192,7 @@ describe("GalleryComponent", () => {

describe("Images Data", () => {
it("should have correct image sources", () => {
expect(component.images[0].src).toBe('./assets/cyber-security.avif');
expect(component.images[0].src).toBe('assets/cyber-security.avif');
expect(component.images[1].src).toBe('assets/digital-strategy.avif');
expect(component.images[2].src).toBe('assets/software-development.avif');
});
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/home/gallery/gallery.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class GalleryComponent implements OnInit {
// TODO: Change href on routerLink, issue #87.
images: GalleryImage[] = [
{
src: './assets/cyber-security.avif',
src: 'assets/cyber-security.avif',
title: 'Cyber Security',
description: 'Learn more about our cyber security services',
link: '/services/cyber-security'
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/home/hero/hero.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- Contains mixing styles in HTML tags within a string, therefore "[innerHtml]" is required to correctly parse the code. -->
<h1 [innerHtml]="headerSafeHtml"></h1>
<div class="contact-btn-wrapper">
<a href="/contact" class="contact-btn">
<a href="/contact" routerLink="/contact" class="contact-btn">
<span>Contact Us</span>
</a>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/home/hero/hero.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { DomSanitizer } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { HeroComponent } from './hero.component';

// Mock particlesJS
Expand All @@ -19,7 +20,7 @@ describe('HeroComponent', () => {
window.particlesJS = jasmine.createSpy('particlesJS');

await TestBed.configureTestingModule({
imports: [HeroComponent],
imports: [HeroComponent, RouterTestingModule],
providers: [
{
provide: DomSanitizer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ <h2 class="testimonials-title">Success stories.</h2>
<ng-template carouselSlide [id]="slidesItem.id">
<div class="block-item">
<img
[alt]="slidesItem.name"
[alt]="'Portrait of ' + slidesItem.name"
[src]="slidesItem.src"
[title]="slidesItem.name"
decoding="async"
height="100"
loading="lazy"
width="100"
/>
<p>
{{ slidesItem.testimonial }}
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/methodology/methodology.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
role="region"
>
<!-- Title of the card. -->
<mat-card-title aria-level="1" role="heading" title="Methodology"
>Methodology</mat-card-title
<mat-card-title title="Methodology"
><h1>Methodology</h1></mat-card-title
>
<mat-card-subtitle aria-level="3" role="heading"
<mat-card-subtitle
>Modern Delivery approach.</mat-card-subtitle
>

Expand Down
5 changes: 4 additions & 1 deletion src/app/components/not-found/not-found.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
><span #renderIcosahedron aria-hidden="true"></span
><strong class="mega" aria-hidden="true">4</strong>
</p>
<p
<h1
aria-label="Page has not been found."
id="mini"
class="error-message"
title="Page not found"
>
Page not found. That's an error &ndash; please try again.
</h1>
<p class="error-home-link">
<a routerLink="/" title="Home">Return to the homepage</a>
</p>
</section>
6 changes: 6 additions & 0 deletions src/app/components/not-found/not-found.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@
display: flex;
justify-content: center;
}

.error-home-link {
display: flex;
justify-content: center;
margin-top: 1rem;
}
3 changes: 2 additions & 1 deletion src/app/components/not-found/not-found.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { Renderer2 } from "@angular/core";
import { RouterTestingModule } from "@angular/router/testing";
import { NotFoundComponent } from "./not-found.component";

describe("NotFoundComponent", () => {
Expand All @@ -15,7 +16,7 @@ describe("NotFoundComponent", () => {
]);

await TestBed.configureTestingModule({
imports: [NotFoundComponent],
imports: [NotFoundComponent, RouterTestingModule],
providers: [
{ provide: Renderer2, useValue: mockRenderer2 },
],
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/not-found/not-found.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Renderer2,
ViewChild,
} from "@angular/core";
import { RouterLink } from "@angular/router";
// @ts-ignore
import * as THREE from 'three';

Expand All @@ -18,7 +19,7 @@ import * as THREE from 'three';
templateUrl: "./not-found.component.html",
styleUrls: ["./not-found.component.scss"],
standalone: true,
imports: []
imports: [RouterLink]
})
export class NotFoundComponent implements AfterViewInit {
@ViewChild("renderIcosahedron", { static: false })
Expand Down
Loading
Loading