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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
### Removed

### Chore
- Updated version of cypress to `14.5.4` and fixed broken cypress tests and removed loading of `tinymce` from `layout.tsx` into `TinyMCEEditor`
- Trying another method of disabling the renovate `Apollo Graphql Group update`
- Disable `Apollo Graphql` renovate PR and fix missing logs from CHANGELOG
- Added `renovate.json` config file in order to get automatic PRs for dependency updates
Expand Down
9 changes: 1 addition & 8 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Metadata } from "next";
import Script from "next/script";
import { Poppins } from "next/font/google";
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
Expand Down Expand Up @@ -56,13 +55,7 @@ export default async function LocaleLayout({

return (
<html lang={locale} className={font_sans_serif.variable}>
<head>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed this from because even with the beforeInteractive which loads it early in the page lifecycle, it was still loading on every page, and most pages don't use TinyMCEEditor.

Copy link
Collaborator

Choose a reason for hiding this comment

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

good catch.

<Script
src="/tinymce/tinymce.min.js"
referrerPolicy="origin"
strategy="beforeInteractive"
/>
</head>
<head></head>
<body className={font_sans_serif.className}>
<a href="#mainContent" className="skip-nav">Skip to main content</a>
<NextIntlClientProvider messages={messages}>
Expand Down
3 changes: 3 additions & 0 deletions components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function Header() {
}, [isAuthenticated]);

const handleLogout = async () => {
setShowMobileMenu(false);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I know this Header is just a temporary filler, but noticed that the mobile menu doesn't close when a user clicks on the "Logout" button in the mobile navigation. This was causing the "logout" cypress test to fail.

try {
const response = await fetch(`${process.env.NEXT_PUBLIC_SERVER_ENDPOINT}/apollo-signout`, {
method: "POST",
Expand Down Expand Up @@ -308,6 +309,7 @@ function Header() {
<li role="menuitem">
<Button
className="react-aria-Button secondary"
data-testid="logoutButtonDesktop"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added test ids for easier testing in cypress

Copy link
Collaborator

Choose a reason for hiding this comment

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

interesting. I've never seen data-testId used before

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea it makes it easier to test specific components.

onPress={handleLogout}
>
{t("btnLogout")}
Expand Down Expand Up @@ -580,6 +582,7 @@ function Header() {
<li role="menuitem">
<Button
className="react-aria-Button secondary"
data-testid="logoutButtonMobile"
onPress={handleLogout}
>
{t("btnLogout")}
Expand Down
5 changes: 4 additions & 1 deletion components/TinyMCEEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect, useRef, useState } from 'react';
import type { Editor as TinyMCEEditorType } from 'tinymce';
import { loadTinymceScript } from '@/utils/loadTinyMCE';
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved loading of tinymce to it's own separate util that is used only when TinyMCEEditor is used on a page.

import styles from './tinyMCEEditor.module.scss';

// We need to reference "window.tinymce" but TypeScript doesn't know about it.
Expand All @@ -24,14 +25,16 @@ interface TinyMCEEditorProps {
helpText?: string;
}


const TinyMCEEditor = ({ content, setContent, onChange, error, id, labelId, helpText }: TinyMCEEditorProps) => {
const editorRef = useRef<TinyMCEEditorType | null>(null); // Update the type here
const elementId = id || 'tiny-editor';
const [isEditorReady, setIsEditorReady] = useState(false);

useEffect(() => {
const initEditor = async () => {
// Ensure tinymce library is available
await loadTinymceScript();

// Make sure previous instance is removed
window.tinymce.remove(`#${elementId}`);

Expand Down
15 changes: 13 additions & 2 deletions cypress/e2e/login.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@
describe('Authentication flow tests', () => {
// Base configuration
const baseUrl = Cypress.env('BASE_URL') || 'http://localhost:3000';
const email = Cypress.env('TEST_USER_EMAIL') || '[email protected]';
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed tests. This was mostly to see if I could get Cypress tests to work, because previously it stopped working when we updated NextJS version.

Glad to see that we can still get cypress to work for the app. We might consider using this for integration tests.

const password = Cypress.env('TEST_USER_PASSWORD') || 'Password123$9';

beforeEach(() => {
// Clear cookies and local storage before each test
cy.clearCookies();
cy.clearLocalStorage();

// Ignore React hydration mismatches so tests can proceed
cy.on('uncaught:exception', (err) => {
if (/Hydration failed/.test(err.message)) {
return false;
}
// let other errors fail the test
return undefined;
});
});

describe('Login functionality', () => {
Expand All @@ -19,7 +30,7 @@ describe('Authentication flow tests', () => {
// Step 1: Enter email
cy.get('[data-testid="emailInput"]')
.should('be.visible')
.type(Cypress.env('TEST_USER_EMAIL'));
.type(email);

cy.get('[data-testid="actionContinue"]')
.should('be.enabled')
Expand All @@ -28,7 +39,7 @@ describe('Authentication flow tests', () => {
// Step 2: Enter password
cy.get('[data-testid="passInput"]')
.should('be.visible')
.type(Cypress.env('TEST_USER_PASSWORD'), { log: false }); // hide password in logs
.type(password, { log: false }); // hide password in logs

cy.get('[data-testid="actionSubmit"]')
.should('be.enabled')
Expand Down
48 changes: 34 additions & 14 deletions cypress/e2e/logout.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,26 @@

// Base configuration
const baseUrl = Cypress.env('BASE_URL') || 'http://localhost:3000';
const email = Cypress.env('TEST_USER_EMAIL') || '[email protected]';
const password = Cypress.env('TEST_USER_PASSWORD') || 'Password123$9';

describe('Authentication flow tests', () => {

beforeEach(() => {
// Clear before each run and ignore React hydration errors if any
cy.clearCookies();
cy.clearLocalStorage();
cy.on('uncaught:exception', (err) => {
if (/Hydration failed/.test(err.message)) {
return false;
}
if (/Failed to execute 'removeChild'/.test(err.message)) {
return false;
}
return undefined;
});
});

describe('Logout functionality on desktop', () => {
beforeEach(() => {
cy.viewport(1280, 720);
Expand All @@ -19,7 +36,7 @@ describe('Authentication flow tests', () => {
// Step 1: Enter email
cy.get('[data-testid="emailInput"]')
.should('be.visible')
.type(Cypress.env('TEST_USER_EMAIL'));
.type(email);

cy.get('[data-testid="actionContinue"]')
.should('be.enabled')
Expand All @@ -28,17 +45,15 @@ describe('Authentication flow tests', () => {
// Step 2: Enter password
cy.get('[data-testid="passInput"]')
.should('be.visible')
.type(Cypress.env('TEST_USER_PASSWORD'), { log: false }); // hide password in logs
.type(password, { log: false }); // hide password in logs

cy.get('[data-testid="actionSubmit"]')
.should('be.enabled')
.click();

//Go to home page
cy.visit(`${baseUrl}/en-US`);

// Click logout button
cy.get('[data-method="delete"]').click();
// Wait for login cookies and redirect to complete
// Without relying on cookies or navigation, wait for logout button in Header
cy.get('[data-testid="logoutButtonDesktop"]', { timeout: 10000 }).should('be.visible').click();

// Verify redirect to login page
cy.url().should('include', '/login');
Expand All @@ -59,7 +74,7 @@ describe('Authentication flow tests', () => {
// Step 1: Enter email
cy.get('[data-testid="emailInput"]')
.should('be.visible')
.type(Cypress.env('TEST_USER_EMAIL'));
.type(email);

cy.get('[data-testid="actionContinue"]')
.should('be.enabled')
Expand All @@ -68,20 +83,25 @@ describe('Authentication flow tests', () => {
// Step 2: Enter password
cy.get('[data-testid="passInput"]')
.should('be.visible')
.type(Cypress.env('TEST_USER_PASSWORD'), { log: false }); // hide password in logs
.type(password, { log: false }); // hide password in logs

cy.get('[data-testid="actionSubmit"]')
.should('be.enabled')
.click();

//Go to home page
cy.visit(`${baseUrl}/en-US`);

// Wait for login cookies and redirect to complete
// Open dropdown menu from hamburger icon
cy.get('#mobile-menu-open').click();
// Ensure the mobile menu has finished its slide-in transition
cy.get('#mobile-navigation')
.should('be.visible')
.and('have.css', 'right', '0px');

//Click logout button
cy.get('[data-method="mobile-delete"]').click();
//Click logout button (mobile) without relying on cookies
cy.get('#mobile-navigation [data-testid="logoutButtonMobile"]', { timeout: 10000 })
.scrollIntoView()
.should('be.visible')
.click();

// Verify redirect to login page
cy.url().should('include', '/login');
Expand Down
39 changes: 34 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"@types/react-dom": "18.3.7",
"@types/sanitize-html": "2.16.0",
"brace-expansion": "2.0.2",
"cypress": "14.4.1",
"cypress": "14.5.4",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Was reminded about this update by renovate, but since I wanted to fix some cypress tests, I created my own branch for this.

"dotenv": "16.5.0",
"eslint": "9.28.0",
"eslint-config-next": "15.3.3",
Expand Down
34 changes: 34 additions & 0 deletions utils/loadTinyMCE.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

let tinymceLoadPromise: Promise<void> | null = null;

export function loadTinymceScript(): Promise<void> {
if (typeof window === 'undefined') return Promise.resolve();
if (window.tinymce) return Promise.resolve();
if (tinymceLoadPromise) return tinymceLoadPromise;

tinymceLoadPromise = new Promise((resolve, reject) => { //singleton pattern - subsequent calls in same page return the same promise
const existing = document.querySelector('script[src="/tinymce/tinymce.min.js"]') as HTMLScriptElement | null;

if (existing) {
// Check if already loaded
if (window.tinymce) {
resolve();
return;
}
// Still loading
existing.addEventListener('load', () => resolve());
existing.addEventListener('error', () => reject(new Error('Failed to load TinyMCE script')));
return;
}

const script = document.createElement('script');
script.src = '/tinymce/tinymce.min.js';
script.referrerPolicy = 'origin';
script.async = true;
script.addEventListener('load', () => resolve());
script.addEventListener('error', () => reject(new Error('Failed to load TinyMCE script')));
document.head.appendChild(script);
});

return tinymceLoadPromise;
}