Skip to content
Open
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
8 changes: 7 additions & 1 deletion .github/workflows/if-nodejs-pr-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,14 @@ jobs:
name: Install dependencies
run: npm ci
- if: steps.packagejson.outputs.exists == 'true'
name: Test
name: Run tests
run: npm test --if-present
- if: steps.packagejson.outputs.exists == 'true'
name: Build project
run: npm run build --if-present
- if: steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest'
name: Run E2E tests (Cypress)
run: npm run test:e2e --if-present
Comment on lines +79 to +81
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the test:e2e script and any server orchestration in package.json
fd -t f '^package\.json$' -d 2 -x cat {} \; | python3 -c "
import json, sys
pkg = json.load(sys.stdin)
scripts = pkg.get('scripts', {})
for k in ['test:e2e', 'cy:run', 'cypress:run', 'dev', 'start']:
    if k in scripts:
        print(f'{k}: {scripts[k]}')
print('---deps---')
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
for d in ['cypress', 'start-server-and-test', 'wait-on', 'concurrently']:
    if d in deps:
        print(f'{d}: {deps[d]}')
"

Repository: asyncapi/website

Length of output: 181


🏁 Script executed:

cat .github/workflows/if-nodejs-pr-testing.yml

Repository: asyncapi/website

Length of output: 7998


🏁 Script executed:

fd -t f 'cypress\.config\.(js|ts|mjs|cjs)$'

Repository: asyncapi/website

Length of output: 78


🏁 Script executed:

cat cypress.config.js

Repository: asyncapi/website

Length of output: 566


The test:e2e script does not orchestrate a server, causing E2E tests to fail.

The script is bare npx cypress run without server orchestration, and the workflow has no step to start a server. Cypress is configured to connect to http://127.0.0.1:3000, but nothing serves on that port when tests run. Use start-server-and-test or similar to wrap cypress run with a server launcher (likely the start script which uses serve@latest out), or integrate server startup into the test:e2e script itself.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/if-nodejs-pr-testing.yml around lines 79 - 81, The E2E job
runs npm run test:e2e (which currently just does npx cypress run) without
starting the app server, so Cypress cannot reach http://127.0.0.1:3000; update
the workflow or the test:e2e script to orchestrate the server startup (e.g., use
start-server-and-test to run the existing start script that uses serve@latest
out and then run cypress, or modify package.json test:e2e to start the server
and wait before invoking npx cypress run) so that npm run test:e2e brings up the
server on port 3000 before Cypress executes.

- if: steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest'
name: Run linter
run: npm run lint --if-present
Expand Down
4 changes: 2 additions & 2 deletions config/tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@
"borderColor": "border-[#CA1A33]"
},
{
"name": "Liquid",
"name": "AsyncAPI CLI",
"color": "bg-[#61d0f2]",
"borderColor": "border-[#40ccf7]"
},
Expand Down Expand Up @@ -1652,7 +1652,7 @@
"borderColor": "border-[#CA1A33]"
},
{
"name": "Liquid",
"name": "AsyncAPI CLI",
"color": "bg-[#61d0f2]",
"borderColor": "border-[#40ccf7]"
},
Expand Down
181 changes: 181 additions & 0 deletions cypress/ambassadors.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
describe('Ambassadors Page - Social Links Rendering', () => {
beforeEach(() => {
cy.visit('/community/ambassadors');
});

describe('Social Links Conditional Rendering', () => {
it('should render ambassador cards', () => {
cy.get('[data-testid="Ambassadors-members"]').should('have.length.greaterThan', 0);
});

it('should not render links with undefined href attributes', () => {
// Check that no anchor tags have href="undefined"
cy.get('[data-testid="Ambassadors-members-socials"] a').each(($link) => {
const href = $link.attr('href');
expect(href).not.to.equal('undefined');
expect(href).not.to.be.empty;
expect(href).to.match(/^https?:\/\//); // Must start with http/https
});
});

it('should only render Twitter links when twitter URL exists', () => {
cy.get('[data-testid="Ambassadors-members"]').each(($card) => {
const twitterLink = cy.wrap($card).find('[data-testid="Ambassadors-members-twitter"]');

// If Twitter link exists, verify it has a valid href
twitterLink.then(($el) => {
if ($el.length > 0) {
expect($el.attr('href')).to.include('twitter.com');
expect($el.attr('href')).not.to.equal('undefined');
}
});
});
});

it('should only render GitHub links when github URL exists', () => {
cy.get('[data-testid="Ambassadors-members-socials"]').each(($socialsDiv) => {
// Get all links in the socials section
const links = cy.wrap($socialsDiv).find('a');

links.each(($link) => {
const href = $link.attr('href');
// If it's a GitHub link, ensure it's valid
if (href && href.includes('github.com')) {

Check warning on line 43 in cypress/ambassadors.cy.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=asyncapi_website&issues=AZ38TnPbbzsaCN7v2Mb8&open=AZ38TnPbbzsaCN7v2Mb8&pullRequest=5285
expect(href).not.to.equal('undefined');
expect(href).to.include('github.com');
}
});
});
});

it('should only render LinkedIn links when linkedin URL exists', () => {
cy.get('[data-testid="Ambassadors-members-socials"]').each(($socialsDiv) => {
// Get all links in the socials section
const links = cy.wrap($socialsDiv).find('a');

links.each(($link) => {
const href = $link.attr('href');
// If it's a LinkedIn link, ensure it's valid
if (href && href.includes('linkedin.com')) {

Check warning on line 59 in cypress/ambassadors.cy.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=asyncapi_website&issues=AZ38TnPbbzsaCN7v2Mb9&open=AZ38TnPbbzsaCN7v2Mb9&pullRequest=5285
expect(href).not.to.equal('undefined');
expect(href).to.include('linkedin.com/in');
}
});
});
});

it('should render valid links with proper target and rel attributes', () => {
cy.get('[data-testid="Ambassadors-members-socials"] a').each(($link) => {
expect($link.attr('target')).to.equal('_blank');
expect($link.attr('rel')).to.include('noreferrer');
});
});
});

describe('Ambassador Images', () => {
it('should not render broken image URLs', () => {
cy.get('[data-testid="Ambassadors-members-img"] img').each(($img) => {
const src = $img.attr('src');
expect(src).not.to.equal('undefined.png');
expect(src).not.to.be.empty;
});
});

it('should load ambassador images without 404 errors', () => {
cy.get('[data-testid="Ambassadors-members-img"] img').each(($img) => {
cy.wrap($img).should('be.visible');
// Verify image is loaded (naturalHeight should be set for successful loads)
cy.wrap($img).should(($el) => {
// Just check that src is valid
expect($el.attr('src')).to.exist;
expect($el.attr('src')).not.to.include('undefined');
});
});
});
});

describe('Social Links Navigation', () => {
it('should navigate to Twitter when clicking Twitter link', () => {
cy.get('[data-testid="Ambassadors-members-twitter"]').first().then(($link) => {
if ($link.length > 0) {
const href = $link.attr('href');
expect(href).to.include('twitter.com');
cy.wrap($link).should('have.attr', 'href', href);
}
});
});

it('should navigate to GitHub when clicking GitHub link', () => {
cy.get('[data-testid="Ambassadors-members-socials"]').first().then(($socialsDiv) => {
const githubLink = cy.wrap($socialsDiv).find('a').filter(':contains("Github")');

githubLink.then(($link) => {
if ($link.length > 0) {
const href = $link.attr('href');
expect(href).to.include('github.com');
}
});
});
});

it('should navigate to LinkedIn when clicking LinkedIn link', () => {
cy.get('[data-testid="Ambassadors-members-socials"]').first().then(($socialsDiv) => {
const linkedinLink = cy.wrap($socialsDiv).find('a').filter(':contains("Linkedin")');

linkedinLink.then(($link) => {
if ($link.length > 0) {
const href = $link.attr('href');
expect(href).to.include('linkedin.com');
}
});
});
});
});

describe('Ambassador Card Layout', () => {
it('should display ambassador name and country', () => {
cy.get('[data-testid="Ambassadors-members-details"]').each(($detail) => {
expect($detail).to.contain.text('');
// Should have name and country
cy.wrap($detail).find('div').should('have.length.at.least', 2);
});
Comment on lines +137 to +141
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tautological / no-op assertions.

expect($detail).to.contain.text('') is always true (every element "contains" the empty string), so this assertion provides no coverage. Similarly, the GitHub edge-case test at Lines 168-171 wraps expect(hasValidLinks).to.be.true inside if (hasValidLinks), which means the assertion can never fail — if hasValidLinks is false the block is skipped, and if it's true the assertion is trivially satisfied.

🛡️ Proposed fix
-    it('should display ambassador name and country', () => {
-      cy.get('[data-testid="Ambassadors-members-details"]').each(($detail) => {
-        expect($detail).to.contain.text('');
-        // Should have name and country
-        cy.wrap($detail).find('div').should('have.length.at.least', 2);
-      });
-    });
+    it('should display ambassador name and country', () => {
+      cy.get('[data-testid="Ambassadors-members-details"]').each(($detail) => {
+        expect($detail.text().trim()).to.not.equal('');
+        expect($detail.find('div').length).to.be.at.least(2);
+      });
+    });
-          // At least some ambassadors should have GitHub
-          if (hasValidLinks) {
-            expect(hasValidLinks).to.be.true;
-          }
+          expect(hasValidLinks, 'at least one ambassador should expose a GitHub link').to.be.true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cypress/ambassadors.cy.ts` around lines 137 - 141, Remove the tautological
assertions and make them meaningful: replace expect($detail).to.contain.text('')
inside the cy.get('[data-testid="Ambassadors-members-details"]').each callback
with a real check (e.g., assert the text is not empty or matches a name/country
pattern, or use cy.wrap($detail).invoke('text').should('not.be.empty') and
confirm the expected children via
cy.wrap($detail).find('div').should('have.length.at.least', 2)). Also remove the
conditional guard around expect(hasValidLinks).to.be.true so the test asserts
the boolean directly (i.e., compute hasValidLinks and then call
expect(hasValidLinks).to.be.true without wrapping it in if (hasValidLinks)),
ensuring the assertion can fail when links are invalid.

});

it('should display ambassador title and bio', () => {
cy.get('[data-testid="Ambassadors-members"]').each(($card) => {
cy.wrap($card).should('be.visible');
// Should have title in rounded border
cy.wrap($card).find('.rounded-lg').should('be.visible');
});
});

it('should display social links section for each ambassador', () => {
cy.get('[data-testid="Ambassadors-members-socials"]').should('have.length.greaterThan', 0);
});
});

describe('Edge Cases', () => {
it('should handle ambassadors with only GitHub', () => {
// Find an ambassador card and verify GitHub link exists
cy.get('[data-testid="Ambassadors-members-socials"]').first().within(() => {
cy.get('a').then(($links) => {
const links = $links.toArray();
const hasValidLinks = links.some((link) => {
const href = link.getAttribute('href');
return href && href.includes('github.com') && href !== 'undefined';

Check warning on line 165 in cypress/ambassadors.cy.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=asyncapi_website&issues=AZ38TnPbbzsaCN7v2Mb-&open=AZ38TnPbbzsaCN7v2Mb-&pullRequest=5285
});

// At least some ambassadors should have GitHub
if (hasValidLinks) {
expect(hasValidLinks).to.be.true;
}
});
});
});

it('should not have any broken hrefs in the entire page', () => {
cy.get('a[href="undefined"]').should('have.length', 0);
cy.get('a[href=""]').should('not.exist');
});
});
});
25 changes: 1 addition & 24 deletions pages/community/ambassadors/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';

import type { Ambassador } from '@/types/pages/community/Community';
import { HeadingTypeStyle } from '@/types/typography/Heading';
import { addAdditionalUserInfo } from '@/utils/ambassadors';

import Button from '../../../components/buttons/Button';
import IconGithub from '../../../components/icons/Github';
Expand All @@ -15,30 +16,6 @@ import Heading from '../../../components/typography/Heading';
import ambassadorList from '../../../config/ambassador_lists.json';
import ambassadors from '../../../config/AMBASSADORS_MEMBERS.json';

/**
* @description Add additional user information to the user object having ambassador data
* @param {Ambassador} user - The user object having ambassador data
*/
export function addAdditionalUserInfo(user: Ambassador) {
const userData: Ambassador = {
...user
};

if (userData.github) {
userData.githubUrl = `https://www.github.com/${userData.github}`;
}
if (userData.linkedin) {
userData.linkedinUrl = `https://www.linkedin.com/in/${userData.linkedin}`;
}
if (userData.twitter) {
userData.twitterUrl = `https://www.twitter.com/${userData.twitter}`;
}

userData.img = `${userData.githubUrl}.png`;

return userData;
}

/**
* @description The main page for the AsyncAPI Ambassador Program.
*/
Expand Down
25 changes: 2 additions & 23 deletions pages/finance.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Head from 'next/head';
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';

import AsyncAPISummary from '../components/FinancialSummary/AsyncAPISummary';
import BarChartComponent from '../components/FinancialSummary/BarChartComponent';
Expand All @@ -8,32 +8,13 @@ import ExpenseBreakdown from '../components/FinancialSummary/ExpenseBreakdown';
import OtherFormsComponent from '../components/FinancialSummary/OtherFormsComponent';
import SponsorshipTiers from '../components/FinancialSummary/SponsorshipTiers';
import SuccessStories from '../components/FinancialSummary/SuccessStories';
import Container from '../components/layout/Container';

/**
* @description The FinancialSummary is the financial summary page of the website.
* It contains the AsyncAPI summary, sponsorship tiers, other forms, expense breakdown,
* bar chart, success stories, and contact us components.
*/
export default function FinancialSummary() {
const [windowWidth, setWindowWidth] = useState<number>(0);

const handleResizeRef = useRef<() => void>(null!);

handleResizeRef.current = () => {
setWindowWidth(window.innerWidth);
};

// Handle window resize event to update the window width state value for responsive design purposes
useEffect(() => {
handleResizeRef.current!();
window.addEventListener('resize', handleResizeRef.current!);

return () => {
window.removeEventListener('resize', handleResizeRef.current!);
};
}, []);

const title = 'AsyncAPI Finance Summary';
const description = 'Financial Summary of AsyncAPI';

Expand All @@ -53,7 +34,5 @@ export default function FinancialSummary() {
</>
);

const shouldUseContainer = windowWidth > 1700;

return <div>{shouldUseContainer ? <Container wide>{renderComponents()}</Container> : renderComponents()}</div>;
return <div className='w-full 2xl:mx-auto 2xl:max-w-screen-xl'>{renderComponents()}</div>;
}
Loading
Loading