From 9d2089cb26c6c2d5fe578365c414508beae2acdd Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Sat, 4 Apr 2026 06:31:29 +1100 Subject: [PATCH 1/2] PM-4685: remove unlimited submissions option What was broken The design challenge editor still showed an Unlimited submissions option even though that control is no longer used and did not work as expected. Root cause The maximum submissions field still rendered the legacy Unlimited checkbox and the editor still carried an update path for that unused metadata toggle. What was changed Removed the editable Unlimited checkbox from the design challenge maximum submissions field. Removed the unused unlimited update branch from challenge metadata handling. Kept the existing read-only display for legacy unlimited metadata so older challenge data still renders consistently. Any added/updated tests Added a MaximumSubmissionsField component test that covers removing the editable Unlimited option, preserving the legacy read-only display, and sanitizing the count input. --- .../MaximumSubmissions-Field/index.js | 18 ---- .../MaximumSubmissions-Field/index.test.js | 82 +++++++++++++++++++ src/components/ChallengeEditor/index.js | 3 - 3 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js diff --git a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js b/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js index 03d0fc88..d35f972e 100644 --- a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js +++ b/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js @@ -34,24 +34,6 @@ class MaximumSubmissionsField extends Component { {!readOnly && (
-
-
-
- onUpdateMetadata('submissionLimit', e.target.checked, 'unlimited')} - /> - -
-
-
diff --git a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js b/src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js new file mode 100644 index 00000000..c484d044 --- /dev/null +++ b/src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js @@ -0,0 +1,82 @@ +/* global describe, it, expect, beforeEach, afterEach, jest */ + +import React from 'react' +import ReactDOM from 'react-dom' +import { act, Simulate } from 'react-dom/test-utils' +import MaximumSubmissionsField from './index' + +describe('MaximumSubmissionsField', () => { + let container + + const renderComponent = (props = {}) => { + act(() => { + ReactDOM.render( + {}} + {...props} + />, + container + ) + }) + } + + beforeEach(() => { + container = document.createElement('div') + document.body.appendChild(container) + }) + + afterEach(() => { + ReactDOM.unmountComponentAtNode(container) + container.remove() + container = null + jest.clearAllMocks() + }) + + it('does not render the unlimited option while editing', () => { + renderComponent({ + challenge: { + metadata: [ + { + name: 'submissionLimit', + value: '{"unlimited":"true","limit":"false","count":""}' + } + ] + } + }) + + expect(container.querySelector('#unlimited')).toBeNull() + expect(container.querySelector('#limit')).not.toBeNull() + expect(container.textContent).not.toContain('Unlimited') + }) + + it('shows unlimited in read-only mode for legacy metadata', () => { + renderComponent({ + readOnly: true, + challenge: { + metadata: [ + { + name: 'submissionLimit', + value: '{"unlimited":"true","limit":"false","count":""}' + } + ] + } + }) + + expect(container.textContent).toContain('Unlimited') + }) + + it('sanitizes the count input before updating metadata', () => { + const onUpdateMetadata = jest.fn() + + renderComponent({ onUpdateMetadata }) + + const countInput = container.querySelector('#count') + + act(() => { + Simulate.change(countInput, { target: { value: '12abc' } }) + }) + + expect(onUpdateMetadata).toHaveBeenCalledWith('submissionLimit', '12', 'count') + }) +}) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 3d21e062..73a97a4f 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -637,9 +637,6 @@ class ChallengeEditor extends Component { if (path === 'count') { submissionLimit.limit = 'true' submissionLimit.unlimited = 'false' - } else if (path === 'unlimited' && value) { - submissionLimit.limit = 'false' - submissionLimit.count = '' } existingMetadata.value = JSON.stringify(submissionLimit) } else if (existingMetadata.name === 'show_data_dashboard') { From b8ba18bbde4ee37a9a33f31c1d87e39eb952e2a1 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 6 Apr 2026 21:16:54 +1000 Subject: [PATCH 2/2] PM-4685: remove submission limit section What was broken The previous fix only removed the editable Unlimited toggle. Design challenges still showed the submission limit section, accepted a limit value that was not enforced, and continued to show a submission limit summary in read-only views. Root cause TextEditorField still rendered MaximumSubmissionsField for design challenges in both edit and read-only flows, and ChallengeEditor still carried dead submissionLimit metadata update logic for that removed behavior. What was changed Removed the submission limit section from design challenge text rendering. Deleted the unused MaximumSubmissionsField component and styles. Removed the obsolete submissionLimit metadata handling path from ChallengeEditor. Any added/updated tests Added TextEditorField tests that verify the submission limit section is hidden in both edit and read-only design challenge views. Removed the obsolete MaximumSubmissionsField component tests with the deleted component. --- .../MaximumSubmissions-Field.module.scss | 131 ------------------ .../MaximumSubmissions-Field/index.js | 81 ----------- .../MaximumSubmissions-Field/index.test.js | 82 ----------- .../ChallengeEditor/TextEditor-Field/index.js | 6 - .../TextEditor-Field/index.test.js | 75 ++++++++++ src/components/ChallengeEditor/index.js | 18 +-- 6 files changed, 76 insertions(+), 317 deletions(-) delete mode 100644 src/components/ChallengeEditor/MaximumSubmissions-Field/MaximumSubmissions-Field.module.scss delete mode 100644 src/components/ChallengeEditor/MaximumSubmissions-Field/index.js delete mode 100644 src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js create mode 100644 src/components/ChallengeEditor/TextEditor-Field/index.test.js diff --git a/src/components/ChallengeEditor/MaximumSubmissions-Field/MaximumSubmissions-Field.module.scss b/src/components/ChallengeEditor/MaximumSubmissions-Field/MaximumSubmissions-Field.module.scss deleted file mode 100644 index d9f97277..00000000 --- a/src/components/ChallengeEditor/MaximumSubmissions-Field/MaximumSubmissions-Field.module.scss +++ /dev/null @@ -1,131 +0,0 @@ -@use '../../../styles/includes' as *; - -.row { - box-sizing: border-box; - display: flex; - flex-direction: row; - margin: 30px 30px 0 30px; - align-content: space-between; - justify-content: flex-start; - - .subGroup { - width: 100%; - display: flex; - flex-direction: column; - margin-bottom: 15px; - - .subRow { - display: flex; - align-items: center; - margin-bottom: 18px; - - input { - width: 100px; - } - - .tcCheckbox { - @include tc-checkbox; - - .tc-checkbox-label { - @include roboto-light(); - - line-height: 17px; - font-weight: 300; - margin-left: 21px; - user-select: none; - cursor: pointer; - width: 195px; - font-size: 14px; - color: #3d3d3d; - } - - height: 18px; - width: 210px; - margin: 0; - padding: 0; - vertical-align: bottom; - position: relative; - display: inline-block; - - input[type=checkbox] { - display: flex; - opacity: 0; - - &:focus ~ label { - &::after { - opacity: 0.3; - } - } - - &:checked ~ label { - background: $tc-blue-20; - - &::after { - opacity: 1; - border-color: $white; - } - } - } - - label { - @include roboto-light(); - - line-height: 17px; - font-weight: 300; - cursor: pointer; - position: absolute; - display: inline-block; - width: 14px; - height: 14px; - top: 0; - left: 0; - border: none; - box-shadow: none; - background: $tc-gray-30; - transition: all 0.15s ease-in-out; - - &::after { - opacity: 0; - content: ''; - position: absolute; - width: 9px; - height: 5px; - background: transparent; - top: 2px; - left: 2px; - border-top: none; - border-right: none; - transform: rotate(-45deg); - transition: all 0.15s ease-in-out; - } - - &:hover::after { - opacity: 0.3; - } - - div { - margin-left: 24px; - width: 75px; - } - } - } - - .tcCheckbox:last-of-type { - width: 100px; - } - } - - .subRow:last-of-type { - margin-bottom: 0; - } - } - .subGroup:last-of-type { - margin-bottom: 0; - } - - -} - -.row:last-of-type { - flex-direction: column; -} diff --git a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js b/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js deleted file mode 100644 index d35f972e..00000000 --- a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.js +++ /dev/null @@ -1,81 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import cn from 'classnames' -import _ from 'lodash' - -import styles from './MaximumSubmissions-Field.module.scss' - -class MaximumSubmissionsField extends Component { - render () { - const { challenge, onUpdateMetadata, readOnly } = this.props - const metadata = challenge.metadata || {} - let existingData = _.find(metadata, { name: 'submissionLimit' }) - let isUnlimited = false - let isLimited = false - let count = '' - if (existingData) { - const value = JSON.parse(existingData.value) - if (value.unlimited === 'true') { - isUnlimited = true - } - if (value.limit === 'true') { - isLimited = true - } - if (value.count) { - count = value.count - } - } - return ( - -
-
- - {readOnly && ( {isUnlimited ? 'Unlimited' : count})} -
-
- {!readOnly && (
-
-
-
- onUpdateMetadata('submissionLimit', e.target.checked, 'limit')} - /> - -
- onUpdateMetadata('submissionLimit', e.target.value.replace(/[^\d]+/g, ''), 'count')} - /> -
-
-
)} -
- ) - } -} - -MaximumSubmissionsField.defaultProps = { - onUpdateMetadata: () => {}, - readOnly: false -} - -MaximumSubmissionsField.propTypes = { - challenge: PropTypes.shape().isRequired, - onUpdateMetadata: PropTypes.func, - readOnly: PropTypes.bool -} - -export default MaximumSubmissionsField diff --git a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js b/src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js deleted file mode 100644 index c484d044..00000000 --- a/src/components/ChallengeEditor/MaximumSubmissions-Field/index.test.js +++ /dev/null @@ -1,82 +0,0 @@ -/* global describe, it, expect, beforeEach, afterEach, jest */ - -import React from 'react' -import ReactDOM from 'react-dom' -import { act, Simulate } from 'react-dom/test-utils' -import MaximumSubmissionsField from './index' - -describe('MaximumSubmissionsField', () => { - let container - - const renderComponent = (props = {}) => { - act(() => { - ReactDOM.render( - {}} - {...props} - />, - container - ) - }) - } - - beforeEach(() => { - container = document.createElement('div') - document.body.appendChild(container) - }) - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container) - container.remove() - container = null - jest.clearAllMocks() - }) - - it('does not render the unlimited option while editing', () => { - renderComponent({ - challenge: { - metadata: [ - { - name: 'submissionLimit', - value: '{"unlimited":"true","limit":"false","count":""}' - } - ] - } - }) - - expect(container.querySelector('#unlimited')).toBeNull() - expect(container.querySelector('#limit')).not.toBeNull() - expect(container.textContent).not.toContain('Unlimited') - }) - - it('shows unlimited in read-only mode for legacy metadata', () => { - renderComponent({ - readOnly: true, - challenge: { - metadata: [ - { - name: 'submissionLimit', - value: '{"unlimited":"true","limit":"false","count":""}' - } - ] - } - }) - - expect(container.textContent).toContain('Unlimited') - }) - - it('sanitizes the count input before updating metadata', () => { - const onUpdateMetadata = jest.fn() - - renderComponent({ onUpdateMetadata }) - - const countInput = container.querySelector('#count') - - act(() => { - Simulate.change(countInput, { target: { value: '12abc' } }) - }) - - expect(onUpdateMetadata).toHaveBeenCalledWith('submissionLimit', '12', 'count') - }) -}) diff --git a/src/components/ChallengeEditor/TextEditor-Field/index.js b/src/components/ChallengeEditor/TextEditor-Field/index.js index 2ffc2bcc..501f04be 100644 --- a/src/components/ChallengeEditor/TextEditor-Field/index.js +++ b/src/components/ChallengeEditor/TextEditor-Field/index.js @@ -5,7 +5,6 @@ import SkillsField from '../SkillsField' import FinalDeliverablesField from '../FinalDeliverables-Field' import StockArtsField from '../StockArts-Field' import SubmssionVisibility from '../SubmissionVisibility-Field' -import MaximumSubmissionsField from '../MaximumSubmissions-Field' import { CHALLENGE_TRACKS } from '../../../config/constants' import styles from './TextEditor-Field.module.scss' import PropTypes from 'prop-types' @@ -123,11 +122,6 @@ class TextEditorField extends Component { onUpdateCheckbox={onUpdateMetadata} readOnly={readOnly} /> - )}
diff --git a/src/components/ChallengeEditor/TextEditor-Field/index.test.js b/src/components/ChallengeEditor/TextEditor-Field/index.test.js new file mode 100644 index 00000000..9e212f01 --- /dev/null +++ b/src/components/ChallengeEditor/TextEditor-Field/index.test.js @@ -0,0 +1,75 @@ +/* global describe, it, expect, beforeEach, afterEach, jest */ + +import React from 'react' +import ReactDOM from 'react-dom' +import { act } from 'react-dom/test-utils' +import TextEditorField from './index' +import { CHALLENGE_TRACKS } from '../../../config/constants' + +jest.mock('../SpecialChallengeField', () => () => null) +jest.mock('../TagsField', () => () => null) +jest.mock('../SkillsField', () => () => null) +jest.mock('../FinalDeliverables-Field', () => () => null) +jest.mock('../StockArts-Field', () => () => null) +jest.mock('../SubmissionVisibility-Field', () => () => null) +jest.mock('../Description-Field', () => () => null) +jest.mock('../ChallengeReviewer-Field', () => () => null) +jest.mock('../../Buttons', () => ({ + PrimaryButton: () => null +})) + +describe('TextEditorField', () => { + let container + + const renderComponent = (props = {}) => { + act(() => { + ReactDOM.render( + , + container + ) + }) + } + + beforeEach(() => { + container = document.createElement('div') + document.body.appendChild(container) + }) + + afterEach(() => { + ReactDOM.unmountComponentAtNode(container) + container.remove() + container = null + jest.clearAllMocks() + }) + + it('does not render submission limit controls for design challenges while editing', () => { + renderComponent() + + expect(container.textContent).not.toContain('Maximum Number of Submissions') + expect(container.textContent).not.toContain('Unlimited') + expect(container.querySelector('#limit')).toBeNull() + expect(container.querySelector('#count')).toBeNull() + }) + + it('does not render a submission limit summary for design challenges in read-only mode', () => { + renderComponent({ + readOnly: true, + challenge: { + trackId: CHALLENGE_TRACKS.DESIGN, + metadata: [ + { + name: 'submissionLimit', + value: '{"unlimited":"true","limit":"false","count":""}' + } + ] + } + }) + + expect(container.textContent).not.toContain('Maximum Number of Submissions') + expect(container.textContent).not.toContain('Unlimited') + }) +}) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 73a97a4f..1df79071 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -622,24 +622,8 @@ class ChallengeEditor extends Component { if (!existingMetadata) { existingMetadata = { name } newChallenge.metadata.push(existingMetadata) - if (name === 'submissionLimit') { - existingMetadata.value = '{}' - } } - if (existingMetadata.name === 'submissionLimit') { - const submissionLimit = JSON.parse(existingMetadata.value) - _.forOwn(submissionLimit, (value, key) => { - if (value === 'true') { - submissionLimit[key] = 'false' - } - }) - submissionLimit[path] = `${value}` - if (path === 'count') { - submissionLimit.limit = 'true' - submissionLimit.unlimited = 'false' - } - existingMetadata.value = JSON.stringify(submissionLimit) - } else if (existingMetadata.name === 'show_data_dashboard') { + if (existingMetadata.name === 'show_data_dashboard') { existingMetadata.value = Boolean(value) } else { existingMetadata.value = `${value}`