diff --git a/packages/moderation/src/data/nags/project-validation.ts b/packages/moderation/src/data/nags/project-validation.ts index 4c0ff39877..d3a55fcd64 100644 --- a/packages/moderation/src/data/nags/project-validation.ts +++ b/packages/moderation/src/data/nags/project-validation.ts @@ -21,7 +21,7 @@ const summaryErrorCodes: readonly ProjectTextValidationCode[] = [ 'text-banned-link', 'summary-link', ] -const summaryWarningCodes: readonly ProjectTextValidationCode[] = ['summary-matches-title'] +const summaryRequiredCodes: readonly ProjectTextValidationCode[] = ['summary-matches-title'] const descriptionErrorCodes: readonly ProjectTextValidationCode[] = [ 'text-slur', 'text-profanity', @@ -151,10 +151,10 @@ export const projectValidationNags: Nag[] = [ defaultMessage: 'Review the project summary', }), description: (context) => - getFailureDescription(context, ['summary'], 'warn', summaryWarningCodes), - status: 'warning', + getFailureDescription(context, ['summary'], 'error', summaryRequiredCodes), + status: 'required', shouldShow: (context) => - getFirstFailure(context, ['summary'], 'warn', summaryWarningCodes) !== undefined, + getFirstFailure(context, ['summary'], 'error', summaryRequiredCodes) !== undefined, link: { path: 'settings', title: defineMessage({ @@ -189,7 +189,7 @@ export const projectValidationNags: Nag[] = [ defaultMessage: 'Clean up the summary', }), description: (context) => getCodedFailureDescription(context, 'summary-special-formatting'), - status: 'warning', + status: 'required', shouldShow: (context) => getCodedFailure(context, 'summary-special-formatting') !== undefined, link: { path: 'settings', diff --git a/packages/moderation/src/locales/en-US/index.json b/packages/moderation/src/locales/en-US/index.json index ee164a750c..0a6cc963e7 100644 --- a/packages/moderation/src/locales/en-US/index.json +++ b/packages/moderation/src/locales/en-US/index.json @@ -366,7 +366,7 @@ "defaultMessage": "The detected slur “{value}” is not allowed." }, "project.text-validation.summary-matches-title": { - "defaultMessage": "A project summary should not be the same as its title." + "defaultMessage": "A project summary cannot be the same as it's title." }, "project.text-validation.summary-too-short": { "defaultMessage": "Your summary is {length, plural, one {# character} other {# characters}}. At least {minChars, plural, one {# character} other {# characters}} is recommended to create an informative and enticing summary." diff --git a/packages/moderation/src/validators/project-fields/index.ts b/packages/moderation/src/validators/project-fields/index.ts index 8dd05714c0..4878df7cab 100644 --- a/packages/moderation/src/validators/project-fields/index.ts +++ b/packages/moderation/src/validators/project-fields/index.ts @@ -84,7 +84,7 @@ const messages = defineMessages({ }, summaryMatchesTitle: { id: 'project.text-validation.summary-matches-title', - defaultMessage: 'A project summary should not be the same as its title.', + defaultMessage: "A project summary cannot be the same as it's title.", }, summaryTooShort: { id: 'project.text-validation.summary-too-short', @@ -138,8 +138,8 @@ export function normalizeProjectFieldText(value: string) { } export function projectSummaryMatchesTitle(summary: string, title: string) { - const normalizedSummary = normalizeProjectFieldText(summary) - const normalizedTitle = normalizeProjectFieldText(title) + const normalizedSummary = normalizeProjectFieldText(summary).replace(/\s+/g, '') + const normalizedTitle = normalizeProjectFieldText(title).replace(/\s+/g, '') return normalizedSummary.length > 0 && normalizedSummary === normalizedTitle } @@ -353,7 +353,7 @@ export function validateProjectSummary( return [ { code: 'summary-matches-title', - severity: 'warn', + severity: 'error', message: messages.summaryMatchesTitle, }, ] @@ -372,7 +372,7 @@ export function validateProjectSummary( if (hasProjectSummaryFormatting(summary) || containsExplicitLink) { results.push({ code: containsExplicitLink ? 'summary-link' : 'summary-special-formatting', - severity: containsExplicitLink ? 'error' : 'warn', + severity: 'error', message: messages.summarySpecialFormatting, }) } diff --git a/packages/moderation/src/validators/project-fields/tests.ts b/packages/moderation/src/validators/project-fields/tests.ts index 5dee5d7bad..3a6c062486 100644 --- a/packages/moderation/src/validators/project-fields/tests.ts +++ b/packages/moderation/src/validators/project-fields/tests.ts @@ -15,8 +15,9 @@ import { validateProjectTitle, } from './index.ts' -test('compares summaries and titles after trimming and Unicode normalization', () => { +test('compares summaries and titles without whitespace and after Unicode normalization', () => { assert.equal(projectSummaryMatchesTitle(' Caf\u00e9 ', 'Cafe\u0301'), true) + assert.equal(projectSummaryMatchesTitle('Project summary', 'Projectsummary'), true) assert.equal(projectSummaryMatchesTitle('Project summary', 'Project title'), false) assert.equal(projectSummaryMatchesTitle('', ''), false) }) @@ -127,7 +128,11 @@ test('validates project summaries', () => { validateProjectSummary(' Caf\u00e9 ', 'Cafe\u0301')[0]?.message.id, 'project.text-validation.summary-matches-title', ) - assert.equal(validateProjectSummary(' Caf\u00e9 ', 'Cafe\u0301')[0]?.severity, 'warn') + assert.equal(validateProjectSummary(' Caf\u00e9 ', 'Cafe\u0301')[0]?.severity, 'error') + assert.equal( + validateProjectSummary(' Caf\u00e9 ', 'Cafe\u0301')[0]?.message.defaultMessage, + "A project summary cannot be the same as it's title.", + ) assert.deepEqual(validateProjectSummary('Short summary', 'Project title'), [ { code: 'summary-too-short', @@ -148,6 +153,7 @@ test('validates project summaries', () => { validateProjectSummary('# Short summary', 'Project title').map(({ code }) => code), ['summary-too-short', 'summary-special-formatting'], ) + assert.equal(validateProjectSummary('# Short summary', 'Project title')[1]?.severity, 'error') assert.equal( validateProjectSummary('# Short summary', 'Project title')[1]?.message.defaultMessage, summaryContentMessage, diff --git a/packages/moderation/src/validators/project-validation/tests.ts b/packages/moderation/src/validators/project-validation/tests.ts index 6a20e0dad2..f1d94719b0 100644 --- a/packages/moderation/src/validators/project-validation/tests.ts +++ b/packages/moderation/src/validators/project-validation/tests.ts @@ -87,6 +87,12 @@ test('reports whether a project has field validation failures', () => { }) assert.equal(hasProjectFieldValidationFailures(validProject), false) assert.equal(hasProjectFieldValidationFailures(invalidProject), true) + assert.equal( + hasProjectFieldValidationFailures( + createProject({ name: 'Ordinary Tools', summary: 'Ordinary Tools' }), + ), + true, + ) }) test('treats version numbers and explicit summary links as errors', () => { @@ -148,3 +154,18 @@ test('reports summary recommendations without invalidating the project', () => { }) assert.equal(hasProjectFieldValidationFailures(project), false) }) + +test('treats summary formatting as a field validation failure', () => { + const project = createProject({ summary: '# A formatted project summary' }) + const result = validateProjectFields(project) + + assert.equal(result.valid, false) + assert.deepEqual( + result.failures.map(({ code, severity }) => ({ code, severity })), + [ + { code: 'summary-too-short', severity: 'warn' }, + { code: 'summary-special-formatting', severity: 'error' }, + ], + ) + assert.equal(hasProjectFieldValidationFailures(project), true) +})