Skip to content
Closed
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
19 changes: 16 additions & 3 deletions src/components/Stepper/CheckoutStepperContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { Col, Row, Stack, Stepper } from '@openedx/paragon';
import { ReactElement, useEffect } from 'react';
import { useLocation } from 'react-router-dom';

import { PurchaseSummary } from '@/components/PurchaseSummary';
import { StepperTitle } from '@/components/Stepper/StepperTitle';
import { AccountDetails, BillingDetails, PlanDetails } from '@/components/Stepper/Steps';
import { CheckoutSubstepKey } from '@/constants/checkout';
import { CheckoutStepKey, CheckoutSubstepKey } from '@/constants/checkout';
import useCurrentStep from '@/hooks/useCurrentStep';

import AcademicSelection from './Steps/AcademicSelection';

const Steps = (): ReactElement => (
<>
<AcademicSelection />
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hide behind feature flag since it is an incomplete feature. See rootloader.ts.

<PlanDetails />
<AccountDetails />
<BillingDetails />
</>
);

// Use ‘Academic selection’ as a fallback only when the value is null or undefined.
const CheckoutStepperContainer = (): ReactElement => {
const { currentStepKey, currentSubstepKey } = useCurrentStep();
const location = useLocation();

const isEssentialsFlow = location.pathname.startsWith('/essentials');

const activeStep = currentStepKey
?? (isEssentialsFlow
? CheckoutStepKey.AcademicSelection
: CheckoutStepKey.AcademicSelection);

useEffect(() => {
const preventUnload = (e: BeforeUnloadEvent) => {
Expand All @@ -25,14 +38,14 @@ const CheckoutStepperContainer = (): ReactElement => {
}
};
window.addEventListener('beforeunload', preventUnload);
// Added safety to force remove the 'beforeunload' event on the global window
return () => {
window.removeEventListener('beforeunload', preventUnload);
};
}, [currentSubstepKey]);

return (
<Stepper activeKey={currentStepKey}>
// when user entered in our dashbord so intial redering Academic selection.
<Stepper activeKey={activeStep}>
<Stack gap={3}>
<Row>
<Col md={12} lg={8}>
Expand Down
23 changes: 23 additions & 0 deletions src/components/Stepper/StepperContent/AcademicSelectionContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AppContext } from '@edx/frontend-platform/react';
import React, { useContext } from 'react';

import { TermsAndConditionsText } from '@/components/TermsAndConditionsText';

const AcademicSelectionContent = () => {
const { authenticatedUser }: AppContextValue = useContext(AppContext);
return (
<>
{authenticatedUser && (
<p className="text-sm text-muted" data-testid="authenticated-user-email">
Logged in as {authenticatedUser.email}
</p>
)}

<div data-testid="terms-and-conditions-wrapper">
<TermsAndConditionsText />
</div>
</>
);
};

export default AcademicSelectionContent;
1 change: 1 addition & 0 deletions src/components/Stepper/StepperContent/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as AcademicSelectionContent } from './AcademicSelectionContent';
export { default as PlanDetailsContent } from './PlanDetailsContent';
export { default as PlanDetailsLoginContent } from './PlanDetailsLoginContent';
export { default as PlanDetailsRegisterContent } from './PlanDetailsRegisterContent';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* eslint-disable import/order */
import { render, screen } from '@testing-library/react';
import React from 'react';
import '@testing-library/jest-dom';

import { AppContext } from '@edx/frontend-platform/react';

import AcademicSelectionContent from '../AcademicSelectionContent';

jest.mock('@/components/TermsAndConditionsText', () => ({
TermsAndConditionsText: () => (
<div data-testid="terms-and-conditions" />
),
}));

describe('AcademicSelectionContent', () => {
const renderWithContext = (contextValue: any) => render(
<AppContext.Provider value={contextValue}>
<AcademicSelectionContent />
</AppContext.Provider>,
);

it('renders authenticated user email when authenticatedUser exists', () => {
renderWithContext({
authenticatedUser: {
email: 'testuser@example.com',
},
});

expect(
screen.getByText('Logged in as testuser@example.com'),
).toBeInTheDocument();

expect(
screen.getByTestId('terms-and-conditions'),
).toBeInTheDocument();
});

it('does NOT render email paragraph when authenticatedUser is null', () => {
renderWithContext({
authenticatedUser: null,
});

expect(
screen.queryByText(/Logged in as/i),
).not.toBeInTheDocument();

expect(
screen.getByTestId('terms-and-conditions'),
).toBeInTheDocument();
});

it('does NOT render email paragraph when authenticatedUser is undefined', () => {
renderWithContext({
authenticatedUser: undefined,
});

expect(
screen.queryByText(/Logged in as/i),
).not.toBeInTheDocument();

expect(
screen.getByTestId('terms-and-conditions'),
).toBeInTheDocument();
});

it('handles authenticatedUser object without email safely', () => {
renderWithContext({
authenticatedUser: {},
});

expect(
screen.getByText('Logged in as'),
).toBeInTheDocument();

expect(
screen.getByTestId('terms-and-conditions'),
).toBeInTheDocument();
});
});
44 changes: 44 additions & 0 deletions src/components/Stepper/Steps/AcademicSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Stack, Stepper } from '@openedx/paragon';
import { Helmet } from 'react-helmet';

import { CheckoutStepKey } from '@/constants/checkout';

const AcademicSelection = () => {
const intl = useIntl();
const eventKey = CheckoutStepKey.AcademicSelection;
return (
<>
<Helmet
title={intl.formatMessage({
id: 'AcademicSelection.title',
defaultMessage: 'Academic Selection',
})}
/>

<Stack gap={4}>
<Stepper.Step
eventKey={eventKey}
title="Academic Selection"
>
<Stack gap={3}>
<h3>
<FormattedMessage
id="AcademicSelection.header"
defaultMessage="Academic Selection"
/>
</h3>
<p>
<FormattedMessage
id="AcademicSelection.comingSoon"
defaultMessage="Coming soon, we will update you."
/>
</p>
</Stack>
</Stepper.Step>
</Stack>
</>
);
};

export default AcademicSelection;
2 changes: 2 additions & 0 deletions src/components/Stepper/Steps/hooks/useStepperContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AcademicSelectionContent,
AccountDetailsContent,
BillingDetailsContent,
BillingDetailsSuccessContent,
Expand All @@ -12,6 +13,7 @@ import useCurrentPage from '@/hooks/useCurrentPage';
type StepperContentComponent = React.FC<{ form?: any }>;

const StepperContentByPage = {
AcademicSelection: AcademicSelectionContent,
PlanDetails: PlanDetailsContent,
PlanDetailsLogin: PlanDetailsLoginContent,
PlanDetailsRegister: PlanDetailsRegisterContent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen } from '@testing-library/react';
import { fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

import { useFormValidationConstraints } from '@/components/app/data';
Expand Down Expand Up @@ -37,11 +37,9 @@ describe('AccountDetailsPage', () => {
it('renders the continue button correctly', () => {
renderStepperRoute(CheckoutPageRoute.AccountDetails, {
config: {},
authenticatedUser: {
userId: 12345,
},
authenticatedUser: { userId: 12345 },
});
validateText('Continue');
expect(screen.getByText('Continue')).toBeInTheDocument();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can remain as validateText since it does essentially the same thing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes

});

it('renders the CompanyNameField component', () => {
Expand All @@ -51,7 +49,9 @@ describe('AccountDetailsPage', () => {
userId: 12345,
},
});
validateText('What is the name of your company or organization?');
expect(
screen.getByText('What is the name of your company or organization?'),
).toBeInTheDocument();
Comment on lines +52 to +54
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can remain as validateText since it does essentially the same thing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes

});

it('renders the CustomUrlField component', () => {
Expand All @@ -61,6 +61,35 @@ describe('AccountDetailsPage', () => {
userId: 12345,
},
});
validateText('Create a custom URL for your team');
expect(
screen.getByText('Create a custom URL for your team'),
).toBeInTheDocument();
});
Comment on lines +64 to +67
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can remain as validateText since it does essentially the same thing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes


it('renders Back button and navigates to Plan Details', () => {
renderStepperRoute(CheckoutPageRoute.AccountDetails, {
config: {},
authenticatedUser: { userId: 12345 },
});

fireEvent.click(screen.getByText('Back'));

// expect(screen.queryByText('Plan Details')).toBeInTheDocument();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this supposed to be commented out?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes

});

it('sets the page title using Helmet', () => {
renderStepperRoute(CheckoutPageRoute.AccountDetails, {
config: {},
authenticatedUser: { userId: 12345 },
});

expect(document.title).toBe('');
});

it('renders the form wrapper element', () => {
renderStepperRoute(CheckoutPageRoute.AccountDetails, {
config: {},
authenticatedUser: { userId: 12345 },
});
});
});
27 changes: 21 additions & 6 deletions src/components/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { createAppRouter, RouterFallback } from './routes';

// @ts-ignore
const ReactQueryDevtoolsProduction = lazy(() => import('@tanstack/react-query-devtools/production').then((d) => ({
testId: 'react-query-devtools-production',
default: d.ReactQueryDevtools,
})));

Expand Down Expand Up @@ -60,25 +61,39 @@ function useReactQueryDevTools() {

const App = () => {
const queryClient = useAppQueryClient();

// Create the app router during render vs. at the top-level of the module to ensure
// the logging and auth modules are initialized before the router is created.
const router = useMemo(() => createAppRouter(queryClient), [queryClient]);

const showReactQueryDevtools = useReactQueryDevTools();

return (
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<QueryClientProvider
client={queryClient}
data-testid="query-client-provider"
>
<ReactQueryDevtools
initialIsOpen={false}
data-testid="react-query-devtools"
/>

{showReactQueryDevtools && (
<Suspense fallback={null}>
<ReactQueryDevtoolsProduction />
<ReactQueryDevtoolsProduction
data-testid="react-query-devtools-production"
/>
</Suspense>
)}
<AppProvider wrapWithRouter={false}>
<AppProvider
wrapWithRouter={false}
data-testid="app-provider"
>
<RouterProvider
router={router}
fallbackElement={<RouterFallback />}
data-testid="router-provider"
fallbackElement={
<RouterFallback data-testid="router-fallback" />
}
/>
</AppProvider>
</QueryClientProvider>
Expand Down
Loading