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
4 changes: 3 additions & 1 deletion src/course-outline/card-header/TitleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ const TitleButton = ({
onClick={onTitleClick}
title={title}
>
{prefixIcon}
<div className="mr-2">
{prefixIcon}
</div>
<span className={`${namePrefix}-card-title mb-0 truncate-1-line`}>
{title}
</span>
Expand Down
30 changes: 17 additions & 13 deletions src/course-outline/card-header/TitleLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@ const TitleLink = ({
namePrefix,
prefixIcon,
}: TitleLinkProps) => (
<Button
as={Link}
variant="tertiary"
data-testid={`${namePrefix}-card-header__title-link`}
className="item-card-header__title-btn align-items-end"
to={titleLink}
title={title}
>
{prefixIcon}
<span className={`${namePrefix}-card-title mb-0 truncate-1-line`}>
{title}
</span>
</Button>
<>
<div className="mr-2">
{prefixIcon}
</div>
<Button
as={Link}
variant="tertiary"
data-testid={`${namePrefix}-card-header__title-link`}
className="item-card-header__title-btn align-items-end"
to={titleLink}
title={title}
>
<span className={`${namePrefix}-card-title mb-0 truncate-1-line text-left`}>
{title}
</span>
</Button>
</>
);

export default TitleLink;
1 change: 1 addition & 0 deletions src/course-outline/section-card/SectionCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const section = {
versionDeclined: null,
errorMessage: null,
downstreamCustomized: [] as string[],
upstreamName: 'Upstream',
},
} satisfies Partial<XBlock> as XBlock;

Expand Down
8 changes: 7 additions & 1 deletion src/course-outline/section-card/SectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,13 @@ const SectionCard = ({
isExpanded={isExpanded}
onTitleClick={handleExpandContent}
namePrefix={namePrefix}
prefixIcon={<UpstreamInfoIcon upstreamInfo={upstreamInfo} />}
prefixIcon={(
<UpstreamInfoIcon
upstreamInfo={upstreamInfo}
size="md"
openSyncModal={openSyncModal}
/>
)}
/>
);

Expand Down
1 change: 1 addition & 0 deletions src/course-outline/subsection-card/SubsectionCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const subsection: XBlock = {
versionDeclined: null,
errorMessage: null,
downstreamCustomized: [] as string[],
upstreamName: 'Upstream',
},
} satisfies Partial<XBlock> as XBlock;

Expand Down
8 changes: 7 additions & 1 deletion src/course-outline/subsection-card/SubsectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,13 @@ const SubsectionCard = ({
isExpanded={isExpanded}
onTitleClick={handleExpandContent}
namePrefix={namePrefix}
prefixIcon={<UpstreamInfoIcon upstreamInfo={upstreamInfo} />}
prefixIcon={(
<UpstreamInfoIcon
upstreamInfo={upstreamInfo}
size="sm"
openSyncModal={openSyncModal}
/>
)}
/>
);

Expand Down
1 change: 1 addition & 0 deletions src/course-outline/unit-card/UnitCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
font-weight: var(--pgn-typography-headings-font-weight);
line-height: var(--pgn-typography-headings-line-height);
color: var(--pgn-color-headings-base);
align-self: center;
}
}
1 change: 1 addition & 0 deletions src/course-outline/unit-card/UnitCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const unit = {
versionDeclined: null,
errorMessage: null,
downstreamCustomized: [] as string[],
upstreamName: 'Upstream',
},
} satisfies Partial<XBlock> as XBlock;

Expand Down
8 changes: 7 additions & 1 deletion src/course-outline/unit-card/UnitCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,13 @@ const UnitCard = ({
title={displayName}
titleLink={getTitleLink(id)}
namePrefix={namePrefix}
prefixIcon={<UpstreamInfoIcon upstreamInfo={upstreamInfo} size="sm" />}
prefixIcon={(
<UpstreamInfoIcon
upstreamInfo={upstreamInfo}
size="xs"
openSyncModal={openSyncModal}
/>
)}
/>
);

Expand Down
1 change: 1 addition & 0 deletions src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface UpstreamChildrenInfo {
export interface UpstreamInfo {
readyToSync: boolean,
upstreamRef: string,
upstreamName: string,
versionSynced: number,
versionAvailable: number | null,
versionDeclined: number | null,
Expand Down
1 change: 1 addition & 0 deletions src/generic/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
@import "./modal-iframe";
@import "./alert-message";
@import "./inplace-text-editor/InplaceTextEditor";
@import "./upstream-info-icon/UpstreamInfoIcon";
47 changes: 47 additions & 0 deletions src/generic/upstream-info-icon/UpstreamInfoIcon.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.upstream-info-icon {
border: 1px solid var(--pgn-color-light-800);
color: var(--pgn-color-primary-500);

&.sync-state {
&:hover {
border-color: var(--pgn-color-primary-500);
background-color: var(--pgn-color-primary-500);
color: white;
}
}

// Sizes with one icon:

&.size-one-md {
width: 32px;
height: 26px;
}

&.size-one-sm {
width: 28px;
height: 22px;
}

&.size-one-xs {
width: 24px;
height: 18px;
}


// Sizes with two icons:

&.size-two-md {
width: 60px;
height: 26px;
}

&.size-two-sm {
width: 46px;
height: 22px;
}

&.size-two-xs {
width: 36px;
height: 18px;
}
}
90 changes: 80 additions & 10 deletions src/generic/upstream-info-icon/UpstreamInfoIcon.test.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,105 @@
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render, screen } from '@testing-library/react';
import {
render, screen, fireEvent, waitFor, initializeMocks,
} from '@src/testUtils';
import { UpstreamInfoIcon, UpstreamInfoIconProps } from '.';

type UpstreamInfo = UpstreamInfoIconProps['upstreamInfo'];
const mockOpenSyncModal = jest.fn();

const renderComponent = (upstreamInfo?: UpstreamInfo) => (
render(
<IntlProvider locale="en">
<UpstreamInfoIcon upstreamInfo={upstreamInfo} />
</IntlProvider>,
<UpstreamInfoIcon upstreamInfo={upstreamInfo} openSyncModal={mockOpenSyncModal} />,
)
);

describe('<UpstreamInfoIcon>', () => {
beforeEach(() => {
initializeMocks();
});

it('should render with link', () => {
renderComponent({ upstreamRef: 'some-ref', errorMessage: null });
renderComponent({
upstreamRef: 'some-ref',
errorMessage: null,
readyToSync: false,
downstreamCustomized: [],
upstreamName: 'Upstream',
});
expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
expect(screen.queryByTitle('The linked library object has updates available.')).not.toBeInTheDocument();
});

it('should render with broken link', () => {
renderComponent({ upstreamRef: 'some-ref', errorMessage: 'upstream error' });
expect(screen.getByTitle('The link to the library item is broken.')).toBeInTheDocument();
renderComponent({
upstreamRef: 'some-ref',
errorMessage: 'upstream error',
readyToSync: false,
downstreamCustomized: [],
upstreamName: 'Upstream',
});
expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
expect(screen.getByTitle('The referenced library or library object is not available.')).toBeInTheDocument();
});

it('should render with ready to sync link and opens the sync modal', async () => {
renderComponent({
upstreamRef: 'some-ref',
errorMessage: null,
readyToSync: true,
downstreamCustomized: [],
upstreamName: 'Upstream',
});

const icon = screen.getByTitle('This item is linked to a library item.');
expect(icon).toBeInTheDocument();
expect(screen.getByTitle('The linked library object has updates available.')).toBeInTheDocument();

fireEvent.click(icon);
await waitFor(() => expect(mockOpenSyncModal).toHaveBeenCalled());
});

it('should render with course overrides', () => {
renderComponent({
upstreamRef: 'some-ref',
errorMessage: null,
readyToSync: false,
downstreamCustomized: ['data'],
upstreamName: 'Upstream',
});

expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
expect(screen.getByTitle('This library reference has course overrides applied.')).toBeInTheDocument();
});

it('should render with ready to sync and course overrides', () => {
renderComponent({
upstreamRef: 'some-ref',
errorMessage: null,
readyToSync: true,
downstreamCustomized: ['data'],
upstreamName: 'Upstream',
});

expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
expect(screen.queryByTitle('This library reference has course overrides applied.')).not.toBeInTheDocument();
expect(screen.getByTitle('The linked library object has updates available.')).toBeInTheDocument();
});

it('should render null without upstream', () => {
const { container } = renderComponent(undefined);
renderComponent(undefined);
const container = screen.getByTestId('redux-provider');
expect(container).toBeEmptyDOMElement();
});

it('should render null without upstreamRf', () => {
const { container } = renderComponent({ upstreamRef: null, errorMessage: null });
renderComponent({
upstreamRef: null,
errorMessage: null,
readyToSync: false,
downstreamCustomized: [],
upstreamName: 'Upstream',
});
const container = screen.getByTestId('redux-provider');
expect(container).toBeEmptyDOMElement();
});
});
Loading