Skip to content

Commit b968ef7

Browse files
committed
QoL improvements
- Longevity -- Add opacity to canvas -- Add custom borders -- Brain age component - add custom numbers -- Create a new section for diet -- Add Cypress tests -- Add Mario music - Articles -- Heading - fixlef/right icons -- Add recommended read section -- Add labels -- Add go to top button - SEO -- Video optimization -- JSON LD Schema - change types -- Fix duplicate canonicals -- Add no index and no follow to certain pages
1 parent 3203c2b commit b968ef7

File tree

203 files changed

+2859
-600
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+2859
-600
lines changed

cypress.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export default defineConfig({
1515
googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN,
1616
googleClientId: process.env.GOOGLE_CLIENT_ID,
1717
googleClientSecret: process.env.GOOGLE_CLIENT_SECRET,
18+
STRAPI_URL:
19+
process.env.STRAPI_URL ||
20+
process.env.CYPRESS_STRAPI_URL ||
21+
'https://strapi.keepsimple.io',
1822
},
1923
},
2024
});

cypress/e2e/articles/articles-links.spec.cy.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
describe('External Links from API', () => {
22
let routes: string[] = [];
3-
const apiUrl =
4-
'https://strapi.keepsimple.io/api/articles?locale=en&fields[1]=newUrl';
53

64
before(() => {
5+
const strapiUrl =
6+
Cypress.env('STRAPI_URL') || 'https://strapi.keepsimple.io';
7+
const apiUrl = `${strapiUrl}/api/articles?locale=en&fields[1]=newUrl`;
8+
79
cy.request(apiUrl).then(response => {
810
routes = response.body.data.map(
911
item =>
@@ -29,9 +31,10 @@ describe('External Links from API', () => {
2931
if (
3032
href &&
3133
href.startsWith('http') &&
32-
!href.includes('http:localhost:3005') &&
34+
!href.includes('http://localhost:3005') &&
3335
!href.includes('linkedin.com') &&
34-
!href.includes('facebook.com')
36+
!href.includes('facebook.com') &&
37+
!href.includes('/uxcore')
3538
) {
3639
cy.request({
3740
url: href,

cypress/e2e/articles/articles.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('template spec', () => {
55
});
66

77
it('Should show a h1', () => {
8-
cy.checkH1('Articles');
8+
cy.checkH1();
99
});
1010

1111
it("Should click and scroll to 'UX Core' section", () => {

cypress/e2e/articles/what-is-ux-core.cy.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ describe('template spec', () => {
55
});
66

77
it('Should show a h1', () => {
8-
cy.checkH1(
9-
'Reintroduction to UX Core - the world’s largest open-source library of nudging strategies and cognitive biases.',
10-
);
8+
cy.checkH1();
119
});
1210

1311
it('verifies all image src URLs are valid', () => {

cypress/e2e/company-management/company-management.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('template spec', () => {
55
});
66

77
it('Should show a h1', () => {
8-
cy.checkH1('Pyramid of Operational Needs');
8+
cy.checkH1();
99
});
1010

1111
it('Should start playing a music', () => {

cypress/e2e/landing-page/landing-page.cy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ describe('template spec', () => {
44
cy.visit(`${Cypress.config().baseUrl}`);
55
});
66

7-
it('should show a h1', () => {
8-
cy.checkH1('Wolf Alexanyan');
7+
it('Should show a h1', () => {
8+
cy.checkH1();
99
});
1010

1111
it('should check external links and their accessibility', () => {
@@ -33,6 +33,6 @@ describe('template spec', () => {
3333
.should('exist')
3434
.click();
3535
cy.url().should('include', '/ru');
36-
cy.checkH1('Вольф Алексанян');
36+
cy.checkH1();
3737
});
3838
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export {};
2+
const PAGE = '/tools/longevity-protocol/about-project';
3+
const DEFAULT_DNA_SRC = '/keepsimple_/assets/longevity/dna/default.mp4';
4+
5+
describe('About Project – /tools/longevity-protocol/about-project', () => {
6+
beforeEach(() => {
7+
cy.viewport(1920, 900);
8+
cy.visit(PAGE);
9+
});
10+
11+
// 1. H1 existence
12+
it('has a visible H1', () => {
13+
cy.checkH1();
14+
});
15+
16+
// 2. DNA canvas: default.mp4 is present, autoplaying and looping
17+
it('renders the default DNA video with autoplay and loop', () => {
18+
cy.get(`video[src="${DEFAULT_DNA_SRC}"]`)
19+
.should('exist')
20+
.should($video => {
21+
expect($video).to.have.attr('autoplay');
22+
expect($video).to.have.attr('loop');
23+
});
24+
});
25+
26+
// 3. All images load without undefined or broken src paths
27+
it('has no images with undefined or broken src paths', () => {
28+
cy.get('img').each($img => {
29+
const src = $img.attr('src');
30+
31+
expect(src, 'img src should be defined').to.not.be.undefined;
32+
expect(
33+
src,
34+
`img src should not contain "undefined": ${src}`,
35+
).to.not.include('undefined');
36+
37+
if (src && src.startsWith('/')) {
38+
cy.request({ url: src, failOnStatusCode: false }).then(res => {
39+
expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt(
40+
400,
41+
);
42+
});
43+
}
44+
});
45+
});
46+
47+
// 4. Basic stats section contains all 5 stats with non-empty values
48+
it('shows all basic stats with non-empty values', () => {
49+
cy.get('[data-cy="basic-stats"]').should('exist');
50+
cy.get('[data-cy="stat-item"]').should('have.length', 5);
51+
52+
cy.get('[data-cy="stat-value"]').each($span => {
53+
const text = $span.text().trim();
54+
expect(text, 'Stat value should not be empty').to.not.be.empty;
55+
expect(text, 'Stat value should not contain "undefined"').to.not.include(
56+
'undefined',
57+
);
58+
});
59+
});
60+
61+
// 5. All internal and external links are valid (reuses checkPageLinks command)
62+
it('has no broken internal or external links', () => {
63+
cy.checkPageLinks();
64+
});
65+
});
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
const PAGE = '/tools/longevity-protocol/habits/diet';
2+
3+
describe('Diet – /tools/longevity-protocol/habits/diet', () => {
4+
describe('Desktop (1920x900)', () => {
5+
beforeEach(() => {
6+
cy.viewport(1920, 900);
7+
cy.visit(PAGE);
8+
});
9+
10+
// 1. H1 existence
11+
it('has a visible H1', () => {
12+
cy.checkH1();
13+
});
14+
15+
// 2. Japanese text existence
16+
it('has Japanese text rendered', () => {
17+
cy.checkJapaneseText();
18+
});
19+
20+
// 3. WhatToEatOrAvoid – click second selectable item, checkbox state updates
21+
it('updates checkbox state when clicking a WhatToEatOrAvoid item', () => {
22+
// Items with role="checkbox" only exist in the "what to eat" section
23+
cy.scrollTo(0, 1600);
24+
cy.get('[data-cy="diet-checkbox"]').eq(0).scrollIntoView();
25+
26+
// First item starts selected – checkmark is present
27+
cy.get('[data-cy="diet-checkbox"]')
28+
.eq(0)
29+
.find('[data-cy="diet-checkmark"]')
30+
.should('exist');
31+
32+
// Click the second selectable item
33+
cy.get('[data-cy="diet-checkbox"]')
34+
.eq(1)
35+
.closest('[data-cy="what-to-eat-or-avoid"]')
36+
.click({ force: true });
37+
38+
// Second item is now checked
39+
cy.get('[data-cy="diet-checkbox"]')
40+
.eq(1)
41+
.find('[data-cy="diet-checkmark"]')
42+
.should('exist');
43+
44+
// First item is now unchecked
45+
cy.get('[data-cy="diet-checkbox"]')
46+
.eq(0)
47+
.find('[data-cy="diet-checkmark"]')
48+
.should('not.exist');
49+
});
50+
51+
// 4. DietResults – click second item → active states update + YourDiet data changes
52+
it('activates second DietResults item and updates YourDiet data', () => {
53+
// Scroll to the DietResults component
54+
cy.get('[data-cy="diet-results-item"]').first().scrollIntoView();
55+
cy.wait(300);
56+
57+
// Dismiss the cookie banner so it does not cover the result items
58+
cy.get('[data-cy="cookie-box-accept"]').click();
59+
60+
// Initial state: first item (id=1) is active, second is not
61+
cy.get('[data-cy="diet-results-item"]')
62+
.eq(0)
63+
.should('have.attr', 'data-active', 'true');
64+
cy.get('[data-cy="diet-results-item"]')
65+
.eq(1)
66+
.should('have.attr', 'data-active', 'false');
67+
68+
// Capture the current YourDiet selected id before the change
69+
cy.get('[data-cy="your-diet"]')
70+
.invoke('attr', 'data-selected-id')
71+
.then(idBefore => {
72+
// Click the inner img of the second item; the img click bubbles to the
73+
// parent div's onClick and avoids any ::after overlay that may block the hit
74+
cy.get('[data-cy="diet-results-item"]')
75+
.eq(1)
76+
.find('img')
77+
.click({ force: true });
78+
79+
cy.wait(300);
80+
81+
// Active states have flipped
82+
cy.get('[data-cy="diet-results-item"]')
83+
.eq(1)
84+
.should('have.attr', 'data-active', 'true');
85+
cy.get('[data-cy="diet-results-item"]')
86+
.eq(0)
87+
.should('have.attr', 'data-active', 'false');
88+
89+
// YourDiet animation was triggered
90+
cy.get('[data-cy="your-diet"]').should(
91+
'have.attr',
92+
'data-active',
93+
'true',
94+
);
95+
96+
// YourDiet is now displaying a different diet entry (data-selected-id changed)
97+
cy.get('[data-cy="your-diet"]')
98+
.invoke('attr', 'data-selected-id')
99+
.should('not.equal', idBefore);
100+
});
101+
});
102+
// 5. All internal and external links are valid (reuses shared checkPageLinks command)
103+
it('has no broken internal or external links', () => {
104+
cy.checkPageLinks();
105+
});
106+
107+
// 6. All images load – no undefined in src paths
108+
it('has no images with undefined or broken src paths', () => {
109+
cy.get('img').each($img => {
110+
const src = $img.attr('src');
111+
112+
expect(src, 'img src should be defined').to.not.be.undefined;
113+
expect(
114+
src,
115+
`img src should not contain "undefined": ${src}`,
116+
).to.not.include('undefined');
117+
118+
if (src && src.startsWith('/')) {
119+
cy.request({ url: src, failOnStatusCode: false }).then(res => {
120+
expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt(
121+
400,
122+
);
123+
});
124+
}
125+
});
126+
});
127+
});
128+
129+
describe('Mobile (390x844)', () => {
130+
beforeEach(() => {
131+
cy.viewport(390, 844);
132+
cy.visit(PAGE);
133+
});
134+
135+
// Mobile modal – tapping the heart image in WhatToEatOrAvoid opens the
136+
// AboutTheProduct modal; closing via the modal close icon dismisses it
137+
it('opens and closes the mobile AboutTheProduct modal on heart image tap', () => {
138+
// Dismiss the cookie banner before any interaction
139+
cy.get('[data-cy="cookie-box-accept"]').click();
140+
141+
// Scroll to the first WhatToEatOrAvoid card that has a heart trigger
142+
cy.get('[data-cy="heart-trigger"]').first().scrollIntoView();
143+
144+
// Tap the heart image – on mobile this opens the modal instead of a tooltip
145+
cy.get('[data-cy="heart-trigger"]')
146+
.first()
147+
.find('img')
148+
.click({ force: true });
149+
150+
// AboutTheProduct content is now visible inside the portal modal
151+
cy.get('[data-cy="about-product"]').should('be.visible');
152+
153+
// Close via the modal close icon
154+
cy.get('[data-cy="modal-close-icon"]').click();
155+
156+
// Modal and its content are gone
157+
cy.get('[data-cy="about-product"]').should('not.exist');
158+
});
159+
});
160+
});
161+
162+
export {};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export {};
2+
const PAGE = '/tools/longevity-protocol/environment';
3+
const DEFAULT_DNA_SRC = '/keepsimple_/assets/longevity/dna/blue.mp4';
4+
5+
describe('Environment – /tools/longevity-protocol/environment', () => {
6+
beforeEach(() => {
7+
cy.viewport(1920, 900);
8+
cy.visit(PAGE);
9+
});
10+
11+
// 1. H1 existence
12+
it('has a visible H1', () => {
13+
cy.checkH1();
14+
});
15+
16+
// 2. DNA canvas: default.mp4 is present, autoplaying and looping
17+
it('renders the default DNA video with autoplay and loop', () => {
18+
cy.get(`video[src="${DEFAULT_DNA_SRC}"]`)
19+
.should('exist')
20+
.should($video => {
21+
expect($video).to.have.attr('autoplay');
22+
expect($video).to.have.attr('loop');
23+
});
24+
});
25+
26+
// 3. All images load without undefined or broken src paths
27+
it('has no images with undefined or broken src paths', () => {
28+
cy.get('img').each($img => {
29+
const src = $img.attr('src');
30+
31+
expect(src, 'img src should be defined').to.not.be.undefined;
32+
expect(
33+
src,
34+
`img src should not contain "undefined": ${src}`,
35+
).to.not.include('undefined');
36+
37+
if (src && src.startsWith('/')) {
38+
cy.request({ url: src, failOnStatusCode: false }).then(res => {
39+
expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt(
40+
400,
41+
);
42+
});
43+
}
44+
});
45+
});
46+
47+
// 4. All internal and external links are valid (reuses checkPageLinks command)
48+
it('has no broken internal or external links', () => {
49+
cy.checkPageLinks();
50+
});
51+
});

0 commit comments

Comments
 (0)