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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import Renderer from 'react-test-renderer/shallow';

import { SPECS_TAB_STATES } from 'actions/page/challenge-details';
import ChallengeDetailsView from 'components/challenge-detail/Specification';

describe('Challenge detail specification Wipro payments', () => {
test('does not render the payment terms section for Wipro group challenges', () => {
const renderer = new Renderer();
renderer.render((
<ChallengeDetailsView
auth={{}}
challenge={{
description: 'Challenge description',
descriptionFormat: 'HTML',
events: [],
groups: ['wipro-group-id'],
legacy: { forumId: 0 },
metadata: [],
phases: [],
track: 'Development',
userDetails: { roles: [] },
}}
challengesUrl="/challenges"
communitiesList={[{
communityId: 'wipro',
groupIds: ['wipro-group-id'],
}]}
savingChallenge={false}
setSpecsTabState={jest.fn()}
specsTabState={SPECS_TAB_STATES.VIEW}
terms={[]}
updateChallenge={jest.fn()}
/>
));

const renderedText = JSON.stringify(renderer.getRenderOutput());

expect(renderedText).toContain('Challenge Overview');
expect(renderedText).not.toContain('Payments');
expect(renderedText).not.toContain('For employees of Wipro Technologies');
});
});
53 changes: 52 additions & 1 deletion __tests__/shared/containers/challenge-detail/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { getDisplayWinners, isWiproRegistrationBlocked } from 'containers/challenge-detail';
import {
buildChallengeLoginUrl,
getDisplayWinners,
isGroupedChallenge,
isGroupedChallengeAccessError,
isWiproRegistrationBlocked,
shouldLoginForGroupedChallenge,
shouldLoginForGroupedChallengeError,
} from 'containers/challenge-detail';

describe('Challenge detail Wipro registration guard', () => {
test('blocks Wipro members when challenge disallows Wipro participation', () => {
Expand Down Expand Up @@ -67,3 +75,46 @@ describe('Challenge detail winners filter', () => {
]);
});
});

describe('Challenge detail grouped challenge login guard', () => {
beforeEach(() => {
document.cookie = 'tc_utm=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
});

test('detects grouped challenge payloads from array or map groups', () => {
expect(isGroupedChallenge({ groups: ['group-id'] })).toBe(true);
expect(isGroupedChallenge({ groups: { 'group-id': true } })).toBe(true);
expect(isGroupedChallenge({ groups: [] })).toBe(false);
expect(isGroupedChallenge({ groups: {} })).toBe(false);
});

test('requires login only for anonymous grouped challenge payloads', () => {
expect(shouldLoginForGroupedChallenge({}, { groups: ['group-id'] })).toBe(true);
expect(shouldLoginForGroupedChallenge({ tokenV3: 'token' }, { groups: ['group-id'] }))
.toBe(false);
expect(shouldLoginForGroupedChallenge({}, { groups: [] })).toBe(false);
});

test('detects grouped challenge access errors for anonymous detail requests', () => {
expect(isGroupedChallengeAccessError({ payload: new Error('Forbidden') })).toBe(true);
expect(isGroupedChallengeAccessError(new Error('You do not have access to this group')))
.toBe(true);
expect(isGroupedChallengeAccessError(new Error('Not Found'))).toBe(false);

expect(shouldLoginForGroupedChallengeError({}, { payload: new Error('Forbidden') }))
.toBe(true);
expect(shouldLoginForGroupedChallengeError({ tokenV3: 'token' }, new Error('Forbidden')))
.toBe(false);
});

test('builds a login URL that preserves the original challenge URL', () => {
const retUrl = 'https://www.topcoder.com/challenges/abc?tab=details#timeline';
const loginUrl = buildChallengeLoginUrl(retUrl);
const parsedUrl = new URL(loginUrl);

expect(`${parsedUrl.origin}${parsedUrl.pathname}`)
.toBe('https://accounts-auth0.topcoder-dev.com/member');
expect(parsedUrl.searchParams.get('retUrl')).toBe(retUrl);
expect(parsedUrl.searchParams.get('utm_source')).toBe('community-app-main');
});
});
25 changes: 25 additions & 0 deletions __tests__/shared/services/submissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,29 @@ describe('submissions service', () => {
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(result.data).toEqual([{ id: 'submission-only-page' }]);
});

it('passes latest and member filters to the submissions API', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({
data: [{ id: 'submission-latest' }],
meta: {
page: 1,
perPage: 100,
totalPages: 1,
},
}),
});

await getChallengeSubmissions('token-v3', 'challenge-id', {
isLatest: true,
memberId: '1001',
});

expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith(
`${baseUrl}?challengeId=challenge-id&perPage=500&page=1&isLatest=true&memberId=1001`,
expect.objectContaining({ method: 'GET' }),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ describe('mm-final-results utilities', () => {
])).toBe(false);
});

it('keeps final Marathon Match results hidden while submissions are still open', () => {
const submissionPhaseChallenge = {
phases: [
{
isOpen: true,
name: 'Submission',
scheduledStartDate: '2030-01-01T00:00:00.000Z',
},
],
};

expect(shouldShowFinalMmResults(submissionPhaseChallenge, [
{
finalRank: 1,
submissions: [
{
finalScore: 100,
},
],
},
])).toBe(false);
});

it('shows final Marathon Match results as soon as a final score is available', () => {
const mmSubmissions = [
{
Expand Down
48 changes: 48 additions & 0 deletions __tests__/shared/utils/mm-review-summations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,54 @@ describe('buildMmSubmissionData', () => {
]);
});

it('keeps latest-only rows compact while preserving member submission count', () => {
const reviewSummations = [
{
aggregateScore: 10,
id: 'summation-old',
isProvisional: true,
reviewedDate: '2026-04-09T04:00:00.000Z',
submissionId: 'submission-old',
submitterHandle: 'ctrucza',
submitterId: '16064986',
},
{
aggregateScore: 20,
id: 'summation-latest',
isProvisional: true,
reviewedDate: '2026-04-09T05:00:00.000Z',
submissionId: 'submission-latest',
submitterHandle: 'ctrucza',
submitterId: '16064986',
},
];
const rawSubmissions = [
{
createdAt: '2026-04-09T05:00:55.279Z',
id: 'submission-latest',
isLatest: true,
memberId: '16064986',
submissionCount: 7,
submittedDate: '2026-04-09T05:00:55.279Z',
submitterHandle: 'ctrucza',
},
];

const result = buildMmSubmissionData(reviewSummations, rawSubmissions);

expect(result).toHaveLength(1);
expect(result[0]).toEqual(expect.objectContaining({
member: 'ctrucza',
submissionCount: 7,
}));
expect(result[0].submissions).toEqual([
expect.objectContaining({
provisionalScore: 20,
submissionId: 'submission-latest',
}),
]);
});

it('uses reviewedDate before import createdAt for review summation times', () => {
const reviewSummations = [
{
Expand Down
19 changes: 0 additions & 19 deletions src/shared/components/challenge-detail/Specification/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,25 +343,6 @@ export default function ChallengeDetailsView(props) {
</div>
)
}
{isWipro && (
<article>
<h2>
Payments
</h2>
<div>
<p>
For employees of Wipro Technologies, following are the payment terms.
Winner(s) will be awarded the reward money/Winner Circle Points (WCPs) on
successful completion and acceptance of the submission by the stakeholder.
Accumulated reward money for the month will be paid through Wipro payroll as part of subsequent
month’s salary (eg. Aug month challenge winners payment will be credited as part of Sept month salary).
WCPs will be credited to winner’s WCP wallet in 3-4 weeks post challenge closure.
For payment of reward money/WCPs, respective country currency conversion will be
considered as per Wipro standard currency conversion guidelines.
</p>
</div>
</article>
)}
</div>
</div>
{ !isTopCrowdChallenge ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Modal } from 'topcoder-react-ui-kit';
import IconClose from 'assets/images/icon-close-green.svg';
import moment from 'moment';

import LoadingIndicator from 'components/LoadingIndicator';
import FailedSubmissionTooltip from '../FailedSubmissionTooltip';
import InReview from '../../icons/in-review.svg';
import Queued from '../../icons/queued.svg';
Expand All @@ -21,7 +22,7 @@ import style from './style.scss';
export default function SubmissionRow({
isMM, isRDM, openHistory, member, submissions, toggleHistory, challengeStatus,
showFinalResults, finalRank, provisionalRank, onShowPopup, rating, viewAsTable,
numWinners, auth, isLoggedIn, isF2F, isBugHunt,
numWinners, auth, isLoggedIn, isF2F, isBugHunt, submissionCount, loadingHistory,
}) {
const submissionList = Array.isArray(submissions) ? submissions : [];
const latestSubmission = submissionList[0] || {};
Expand Down Expand Up @@ -70,7 +71,7 @@ export default function SubmissionRow({
};

const getFinalReviewResult = () => {
if (_.isNil(finalScore)) {
if (!showFinalResults || _.isNil(finalScore)) {
return 'N/A';
}
if (finalScore < 0) {
Expand Down Expand Up @@ -114,7 +115,9 @@ export default function SubmissionRow({
const memberLinkTarget = `${_.includes(window.origin, 'www') ? '_self' : '_blank'}`;
const memberForHistory = memberHandle || memberDisplay;
const latestSubmissionId = latestSubmission.submissionId || latestSubmission.id || 'N/A';
const submissionCount = submissionList.length;
const displaySubmissionCount = _.isFinite(submissionCount)
? submissionCount
: submissionList.length;

return (
<div styleName={`wrapper ${viewAsTable ? 'wrapper-as-table' : ''} `}>
Expand Down Expand Up @@ -181,7 +184,7 @@ export default function SubmissionRow({
>
<span styleName="text">
History (
{submissionCount}
{displaySubmissionCount}
)
</span>
</a>
Expand Down Expand Up @@ -237,7 +240,7 @@ export default function SubmissionRow({
>
<span styleName="text">
History (
{submissionCount}
{displaySubmissionCount}
)
</span>
</a>
Expand Down Expand Up @@ -305,7 +308,9 @@ export default function SubmissionRow({
</div>
<div styleName="table-body">
{
submissionList.map((submissionHistory, index) => (
loadingHistory ? (
<LoadingIndicator />
) : submissionList.map((submissionHistory, index) => (
<SubmissionHistoryRow
showFinalResults={showFinalResults}
isMM={isMM}
Expand Down Expand Up @@ -347,6 +352,8 @@ SubmissionRow.defaultProps = {
isLoggedIn: false,
isF2F: false,
isBugHunt: false,
loadingHistory: false,
submissionCount: null,
};

SubmissionRow.propTypes = {
Expand Down Expand Up @@ -397,6 +404,8 @@ SubmissionRow.propTypes = {
onShowPopup: PT.func.isRequired,
viewAsTable: PT.bool.isRequired,
isLoggedIn: PT.bool,
loadingHistory: PT.bool,
numWinners: PT.number.isRequired,
auth: PT.shape().isRequired,
submissionCount: PT.number,
};
Loading
Loading