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
6 changes: 2 additions & 4 deletions src/learning-header/LearningHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,11 @@ const LearningHeader = ({
<div className="flex-grow-1 course-title-lockup d-flex" style={{ lineHeight: 1 }}>
<CourseInfoSlot courseOrg={courseOrg} courseNumber={courseNumber} courseTitle={courseTitle} />
</div>
{showUserDropdown && authenticatedUser && (
<>
<LearningHeaderActionsSlot />
<LearningHeaderActionsSlot showDefaultActions={showUserDropdown && !!authenticatedUser} />
{authenticatedUser && showUserDropdown && (
<AuthenticatedUserDropdown
username={authenticatedUser.username}
/>
</>
)}
{showUserDropdown && !authenticatedUser && (
<AnonymousUserMenu />
Expand Down
55 changes: 55 additions & 0 deletions src/learning-header/LearningHeader.test.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
import React from 'react';
import { mergeConfig } from '@edx/frontend-platform';
import { AppContext } from '@edx/frontend-platform/react';
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
import {
authenticatedUser, initializeMockApp, render, screen,
} from '../setupTest';
import { LearningHeader as Header } from '../index';

const V2_SLOT_ID = 'org.openedx.frontend.layout.learning_header_actions.v2';
const V2_WIDGET_TEXT = 'Test V2 Widget';

const configureV2Widget = () => {
mergeConfig({
pluginSlots: {
[V2_SLOT_ID]: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'test_v2_widget',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => <span>{V2_WIDGET_TEXT}</span>,
},
},
],
},
},
});
};

describe('Header', () => {
beforeAll(async () => {
// We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`.
Expand All @@ -26,4 +53,32 @@ describe('Header', () => {
expect(screen.getByText(`${courseData.courseOrg} ${courseData.courseNumber}`)).toBeInTheDocument();
expect(screen.getByText(courseData.courseTitle)).toBeInTheDocument();
});

it('hides the user dropdown and the header actions when showUserDropdown is false', () => {
render(<Header showUserDropdown={false} />);

expect(screen.queryByText(authenticatedUser.username)).not.toBeInTheDocument();
expect(screen.queryByText('Help')).not.toBeInTheDocument();
});
Comment on lines +57 to +62

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.

This test passes unchanged on master.

I ran it against the base files and all three cases pass, so it guards pre-existing hiding behavior and covers no new path. Add a case with a widget configured on .v2 and showUserDropdown={false}, asserting the widget appears, plus one for the anonymous case. That needs a mergeConfig({ pluginSlots: { ... } }) setup, which this repo has no precedent for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated: 3ae9441


it('renders v2 slot content for an authenticated user even when showUserDropdown is false', () => {
configureV2Widget();
render(<Header showUserDropdown={false} />);

expect(screen.getByText(V2_WIDGET_TEXT)).toBeInTheDocument();
expect(screen.queryByText(authenticatedUser.username)).not.toBeInTheDocument();
expect(screen.queryByText('Help')).not.toBeInTheDocument();
});

it('renders v2 slot content for an anonymous user', () => {
configureV2Widget();
render(
<AppContext.Provider value={{ authenticatedUser: null }}>
<Header />
</AppContext.Provider>,
);

expect(screen.getByText(V2_WIDGET_TEXT)).toBeInTheDocument();
expect(screen.queryByText('Help')).not.toBeInTheDocument();
});
});
9 changes: 5 additions & 4 deletions src/plugin-slots/HeaderNotificationsSlot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This slot renders the notifications tray (bell icon + notification popover) from
1. **Desktop Header** — via `org.openedx.frontend.layout.header_desktop_secondary_menu.v2`
Notifications appear before secondary menu items (e.g., "New", "Help")

2. **Learning Header** — via `org.openedx.frontend.layout.learning_header_actions.v1`
2. **Learning Header** — via `org.openedx.frontend.layout.learning_header_actions.v1` (nested within [`.v2`](../LearningHeaderActionsSlot/v2/))
Notifications appear before the help link

3. **Studio Header** — via `org.openedx.frontend.layout.studio_header_actions.v1`
Expand All @@ -27,9 +27,10 @@ Desktop Header
└── org.openedx.frontend.layout.header_desktop_secondary_menu.v1 (menu items only)

Learning Header
└── org.openedx.frontend.layout.learning_header_actions.v1
├── org.openedx.frontend.layout.header_notifications_tray.v1 ← This slot
└── org.openedx.frontend.layout.header_learning_help.v1
└── org.openedx.frontend.layout.learning_header_actions.v2
└── org.openedx.frontend.layout.learning_header_actions.v1 (only when showUserDropdown is true and a user is authenticated)
├── org.openedx.frontend.layout.header_notifications_tray.v1 ← This slot
└── org.openedx.frontend.layout.header_learning_help.v1

Studio Header
└── org.openedx.frontend.layout.studio_header_actions.v1
Expand Down
117 changes: 4 additions & 113 deletions src/plugin-slots/LearningHeaderActionsSlot/README.md
Original file line number Diff line number Diff line change
@@ -1,115 +1,6 @@
# Learning Header Actions Slot

### Slot ID: `org.openedx.frontend.layout.learning_header_actions.v1`

**Default Content:**
- **Notification Tray** (via `HeaderNotificationsSlot`) — Rendered before the help link
- **Help Link** (via `LearningHelpSlot`)

---

### Add Custom Components before and after Learning Header Actions

The following `env.config.jsx` inserts a custom component before the notification tray (`priority: 10`) and another after the help link (`priority: 90`).

![Screenshot of custom components before and after learning header actions](./images/custom_components_before_and_after_learning_actions.png)

```jsx
import React from 'react';
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.learning_header_actions.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_learning_actions',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h1 style={{ textAlign: 'center' }}>🌜</h1>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_learning_actions',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h1 style={{ textAlign: 'center' }}>🌛</h1>
),
},
},
],
},
},
};

export default config;
```

### Hide the Entire Learning Header Actions Area

The following `env.config.jsx` removes both the notification tray and the help link from the learning header.

![Screenshot of hiding learning header actions area](./images/hide_learning_actions.png)

```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.learning_header_actions.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
],
},
},
};

export default config;
```

### Replace the Entire Learning Header Actions Area with a Custom Component

The following `env.config.jsx` replaces the notification tray and help link with a single custom component.

![Screenshot of replacing learning header actions area with custom component](./images/replace_learning_actions_with_custom_component.png)

```jsx
import React from 'react';
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.learning_header_actions.v1': {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_learning_actions',
type: DIRECT_PLUGIN,
priority: 50,
RenderWidget: () => (
<span>My Custom Learning Actions</span>
),
},
},
],
},
},
};

export default config;
```

| Slot ID | Description | Docs |
|---------|-------------|------|
| `org.openedx.frontend.layout.learning_header_actions.v2` | Always rendered, regardless of `showUserDropdown` (Default slot) | [v2 docs](./v2/) |
| `org.openedx.frontend.layout.learning_header_actions.v1` **(Deprecated)** | Notification tray + help link, only rendered when `showUserDropdown` is `true` and a user is authenticated | [v1 docs](./v1/) |
15 changes: 2 additions & 13 deletions src/plugin-slots/LearningHeaderActionsSlot/index.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import HeaderNotificationsSlot from '../HeaderNotificationsSlot';
import LearningHelpSlot from '../LearningHelpSlot';

const LearningHeaderActionsSlot = () => (
<PluginSlot
id="org.openedx.frontend.layout.learning_header_actions.v1"
>
<HeaderNotificationsSlot />
<LearningHelpSlot />
</PluginSlot>
);
import LearningHeaderActionsSlot from './v2';

export { default as LearningHeaderActionsSlotV1 } from './v1';
export default LearningHeaderActionsSlot;
123 changes: 123 additions & 0 deletions src/plugin-slots/LearningHeaderActionsSlot/v1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Learning Header Actions Slot — v1 (Notification Tray + Help Link)

> **⚠️ Deprecated:** This slot is deprecated and will be removed. Use the
> [`v2` slot](../v2/) instead, which renders unconditionally regardless of
> `showUserDropdown`. See [DEPR ticket #681](https://github.com/openedx/frontend-component-header/issues/681)
> for the removal plan and timeline.

### Slot ID: `org.openedx.frontend.layout.learning_header_actions.v1`

**Default Content:**
- **Notification Tray** (via `HeaderNotificationsSlot`) — Rendered before the help link
- **Help Link** (via `LearningHelpSlot`)

> **Note:** This slot is only rendered when `showUserDropdown` is `true` and a user is authenticated. To render content regardless of `showUserDropdown` or authentication, use the parent [`v2` slot](../v2/).

---

## Examples

### Add Custom Components before and after Learning Header Actions

The following `env.config.jsx` inserts a custom component before the notification tray (`priority: 10`) and another after the help link (`priority: 90`).

![Screenshot of custom components before and after learning header actions](../images/custom_components_before_and_after_learning_actions.png)

```jsx
import React from 'react';
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.learning_header_actions.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_before_learning_actions',
type: DIRECT_PLUGIN,
priority: 10,
RenderWidget: () => (
<h1 style={{ textAlign: 'center' }}>🌜</h1>
),
},
},
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_after_learning_actions',
type: DIRECT_PLUGIN,
priority: 90,
RenderWidget: () => (
<h1 style={{ textAlign: 'center' }}>🌛</h1>
),
},
},
],
},
},
};

export default config;
```

### Hide the Entire Learning Header Actions Area

The following `env.config.jsx` removes both the notification tray and the help link from the learning header.

![Screenshot of hiding learning header actions area](../images/hide_learning_actions.png)

```jsx
import { PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.learning_header_actions.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
],
},
},
};

export default config;
```

### Replace the Entire Learning Header Actions Area with a Custom Component

The following `env.config.jsx` replaces the notification tray and help link with a single custom component.

![Screenshot of replacing learning header actions area with custom component](../images/replace_learning_actions_with_custom_component.png)

```jsx
import React from 'react';
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
'org.openedx.frontend.layout.learning_header_actions.v1': {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_learning_actions',
type: DIRECT_PLUGIN,
priority: 50,
RenderWidget: () => (
<span>My Custom Learning Actions</span>
),
},
},
],
},
},
};

export default config;
```
Loading
Loading