mirror of
https://github.com/modrinth/code.git
synced 2026-08-30 11:36:05 +00:00
feat: remove loader detection from project name and set version as required not allowed
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
autocomplete="off"
|
||||
:disabled="hasHitLimit"
|
||||
@update:model-value="updatedName()"
|
||||
@blur="nameForValidation = name"
|
||||
/>
|
||||
<ValidationMessage :check="nameValidation" />
|
||||
</div>
|
||||
@@ -334,7 +335,8 @@ const visibilities = ref<VisibilityOption[]>([
|
||||
])
|
||||
const visibility = ref<VisibilityOption>(visibilities.value[0])
|
||||
|
||||
const nameValidation = useProjectTitleValidation(name)
|
||||
const nameForValidation = ref(name.value)
|
||||
const nameValidation = useProjectTitleValidation(nameForValidation)
|
||||
const summaryValidation = useProjectSummaryValidation(description, name)
|
||||
|
||||
const disableCreate = computed(() => {
|
||||
@@ -514,6 +516,7 @@ async function createProject() {
|
||||
|
||||
async function show(event?: MouseEvent, options?: ShowOptions) {
|
||||
name.value = ''
|
||||
nameForValidation.value = name.value
|
||||
slug.value = ''
|
||||
description.value = ''
|
||||
manualSlug.value = false
|
||||
|
||||
@@ -59,14 +59,14 @@
|
||||
<div
|
||||
v-for="nag in visibleNags"
|
||||
:key="nag.id"
|
||||
class="flex w-[268px] shrink-0 flex-col gap-3 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
|
||||
class="flex w-[268px] shrink-0 flex-col gap-2.5 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
|
||||
>
|
||||
<span class="flex items-center gap-2 font-medium text-contrast">
|
||||
<span class="flex items-start gap-2 font-medium text-contrast">
|
||||
<component
|
||||
:is="nag.icon || getDefaultIcon(nag.status)"
|
||||
v-tooltip="getStatusTooltip(nag.status)"
|
||||
:class="[
|
||||
'size-4',
|
||||
'mt-0.5 size-4 min-w-4',
|
||||
nag.status === 'required' && 'text-red',
|
||||
nag.status === 'warning' && 'text-orange',
|
||||
nag.status === 'suggestion' && 'text-purple',
|
||||
@@ -123,7 +123,7 @@ import {
|
||||
SendIcon,
|
||||
TriangleAlertIcon,
|
||||
} from '@modrinth/assets'
|
||||
import type { Nag, NagContext, NagStatus, ProjectTitleMetadata } from '@modrinth/moderation'
|
||||
import type { Nag, NagContext, NagStatus } from '@modrinth/moderation'
|
||||
import { nags, validateProjectFields } from '@modrinth/moderation'
|
||||
import { Accordion, Button, IconButton } from '@modrinth/ui'
|
||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@modrinth/ui'
|
||||
@@ -327,14 +327,7 @@ watch(nagScroller, (el, previousEl) => {
|
||||
nextTick(updateNagScrollShadows)
|
||||
})
|
||||
|
||||
const titleMetadata = computed<ProjectTitleMetadata>(() => ({
|
||||
gameVersions: props.tags.gameVersions.map(({ version }) => version),
|
||||
loaders: props.tags.loaders.map(({ name }) => name),
|
||||
}))
|
||||
|
||||
const projectValidation = computed(() =>
|
||||
validateProjectFields(props.projectV3, titleMetadata.value),
|
||||
)
|
||||
const projectValidation = computed(() => validateProjectFields(props.projectV3))
|
||||
|
||||
const nagContext = computed<NagContext>(() => ({
|
||||
project: props.project,
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
type LinkCheckContext,
|
||||
type LinkCheckResult,
|
||||
type ProjectTextValidationResult,
|
||||
type ProjectTitleMetadata,
|
||||
validateProjectDescription,
|
||||
validateProjectSummary,
|
||||
validateProjectTitle,
|
||||
@@ -20,18 +19,8 @@ export const projectTextValidationMessages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
function useProjectTitleMetadata() {
|
||||
const generatedState = useGeneratedState()
|
||||
|
||||
return computed<ProjectTitleMetadata>(() => ({
|
||||
gameVersions: generatedState.value.gameVersions.map(({ version }) => version),
|
||||
loaders: generatedState.value.loaders.map(({ name }) => name),
|
||||
}))
|
||||
}
|
||||
|
||||
export function useProjectTitleValidation(text: MaybeRefOrGetter<string | null | undefined>) {
|
||||
const metadata = useProjectTitleMetadata()
|
||||
return computed(() => validateProjectTitle(toValue(text), metadata.value))
|
||||
return computed(() => validateProjectTitle(toValue(text)))
|
||||
}
|
||||
|
||||
export function useProjectSummaryValidation(
|
||||
|
||||
@@ -37,7 +37,7 @@ const {
|
||||
current,
|
||||
saving,
|
||||
hasChanges,
|
||||
reset,
|
||||
reset: resetForm,
|
||||
save: saveForm,
|
||||
} = useSavable(
|
||||
() => ({
|
||||
@@ -57,7 +57,8 @@ const {
|
||||
|
||||
const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
|
||||
|
||||
const titleValidation = useProjectTitleValidation(() => current.value.title)
|
||||
const titleForValidation = ref(current.value.title)
|
||||
const titleValidation = useProjectTitleValidation(titleForValidation)
|
||||
const taglineValidation = useProjectSummaryValidation(
|
||||
() => current.value.tagline,
|
||||
() => current.value.title,
|
||||
@@ -85,6 +86,11 @@ async function save() {
|
||||
await saveForm()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetForm()
|
||||
titleForValidation.value = current.value.title
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
nameTitle: {
|
||||
id: 'project.settings.general.name.title',
|
||||
@@ -198,6 +204,7 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
|
||||
autocomplete="off"
|
||||
:maxlength="50"
|
||||
wrapper-class="flex-grow"
|
||||
@blur="titleForValidation = current.title"
|
||||
/>
|
||||
</div>
|
||||
<ValidationMessage :check="titleValidation" class="mt-2" />
|
||||
|
||||
@@ -22,7 +22,14 @@
|
||||
<label for="project-name">
|
||||
<span class="label__title">Name</span>
|
||||
</label>
|
||||
<Input id="project-name" v-model="name" :maxlength="2048" :disabled="!hasPermission" />
|
||||
<Input
|
||||
id="project-name"
|
||||
v-model="name"
|
||||
:maxlength="2048"
|
||||
wrapper-class="w-full max-w-72"
|
||||
:disabled="!hasPermission"
|
||||
@blur="nameForValidation = name"
|
||||
/>
|
||||
<ValidationMessage :check="nameValidation" class="mt-2" />
|
||||
</div>
|
||||
|
||||
@@ -430,7 +437,8 @@ const hasPermission = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const nameValidation = useProjectTitleValidation(name)
|
||||
const nameForValidation = ref(name.value)
|
||||
const nameValidation = useProjectTitleValidation(nameForValidation)
|
||||
const summaryValidation = useProjectSummaryValidation(summary, name)
|
||||
const hasValidationIssues = computed(
|
||||
() =>
|
||||
@@ -537,6 +545,7 @@ const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
|
||||
|
||||
function resetChanges() {
|
||||
name.value = project.value.name
|
||||
nameForValidation.value = name.value
|
||||
slug.value = project.value.slug ?? ''
|
||||
summary.value = project.value.summary
|
||||
visibility.value = tags.value.approvedStatuses.includes(project.value.status)
|
||||
|
||||
@@ -222,7 +222,6 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const generatedState = useGeneratedState()
|
||||
const debugValidationFilter = useDebugLogger('moderation-validation-filter')
|
||||
const debugProjectIdsFilter = useDebugLogger('moderation-project-ids-filter')
|
||||
|
||||
@@ -598,10 +597,6 @@ const {
|
||||
const response = await scanProjectsWithValidationIssues({
|
||||
client,
|
||||
request: queryKey[3],
|
||||
titleMetadata: {
|
||||
gameVersions: generatedState.value.gameVersions.map(({ version }) => version),
|
||||
loaders: generatedState.value.loaders.map(({ name }) => name),
|
||||
},
|
||||
includeWarnings: queryKey[2],
|
||||
signal,
|
||||
log: debugValidationFilter,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AbstractModrinthClient, Labrinth } from '@modrinth/api-client'
|
||||
import { type ProjectTitleMetadata, validateProjectFields } from '@modrinth/moderation'
|
||||
import { validateProjectFields } from '@modrinth/moderation'
|
||||
|
||||
export type ValidationFilterRequest = Omit<
|
||||
Labrinth.Moderation.Internal.ProjectsRequest,
|
||||
@@ -16,7 +16,6 @@ export interface ModerationQueueFetchOptions {
|
||||
interface ValidationFilterScanOptions {
|
||||
client: AbstractModrinthClient
|
||||
request: ValidationFilterRequest
|
||||
titleMetadata: ProjectTitleMetadata
|
||||
includeWarnings: boolean
|
||||
signal: AbortSignal
|
||||
log: (message: string) => void
|
||||
@@ -130,7 +129,6 @@ export async function fetchAllModerationQueueProjects(
|
||||
export async function scanProjectsWithValidationIssues({
|
||||
client,
|
||||
request,
|
||||
titleMetadata,
|
||||
includeWarnings,
|
||||
signal,
|
||||
log,
|
||||
@@ -170,7 +168,7 @@ export async function scanProjectsWithValidationIssues({
|
||||
if (!project) {
|
||||
throw new Error(`V3 projects response omitted queued project ${projectId}`)
|
||||
}
|
||||
const validation = validateProjectFields(project, titleMetadata)
|
||||
const validation = validateProjectFields(project)
|
||||
if (includeWarnings ? validation.failures.length > 0 : !validation.valid) {
|
||||
matchingProjectIds.add(projectId)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ const nameErrorCodes: readonly ProjectTextValidationCode[] = [
|
||||
'text-profanity',
|
||||
'text-non-standard',
|
||||
]
|
||||
const nameWarningCodes: readonly ProjectTextValidationCode[] = [
|
||||
'title-game-version',
|
||||
'title-loader',
|
||||
]
|
||||
const summaryErrorCodes: readonly ProjectTextValidationCode[] = [
|
||||
'text-slur',
|
||||
'text-profanity',
|
||||
@@ -93,15 +89,14 @@ export const projectValidationNags: Nag[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'project-name-metadata',
|
||||
id: 'project-name-version',
|
||||
title: defineMessage({
|
||||
id: 'nags.project-name-metadata.title',
|
||||
defaultMessage: 'Remove technical details from the name',
|
||||
id: 'nags.project-name-version.title',
|
||||
defaultMessage: 'Fix project name',
|
||||
}),
|
||||
description: (context) => getFailureDescription(context, ['name'], 'warn', nameWarningCodes),
|
||||
status: 'warning',
|
||||
shouldShow: (context) =>
|
||||
getFirstFailure(context, ['name'], 'warn', nameWarningCodes) !== undefined,
|
||||
description: (context) => getCodedFailureDescription(context, 'title-version-number'),
|
||||
status: 'required',
|
||||
shouldShow: (context) => getCodedFailure(context, 'title-version-number') !== undefined,
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
|
||||
@@ -227,8 +227,8 @@
|
||||
"nags.multiple-resolution-tags.title": {
|
||||
"defaultMessage": "Select correct resolution"
|
||||
},
|
||||
"nags.project-name-metadata.title": {
|
||||
"defaultMessage": "Remove technical details from the name"
|
||||
"nags.project-name-version.title": {
|
||||
"defaultMessage": "Fix project name"
|
||||
},
|
||||
"nags.project-summary-content.title": {
|
||||
"defaultMessage": "Review the project summary"
|
||||
@@ -351,7 +351,7 @@
|
||||
"defaultMessage": "Visit links settings"
|
||||
},
|
||||
"project.text-validation.description-profanity": {
|
||||
"defaultMessage": "Excessive profanity is not allowed. Detected: {values}."
|
||||
"defaultMessage": "Excessive profanity is not allowed. Detected: {values}"
|
||||
},
|
||||
"project.text-validation.non-standard-text": {
|
||||
"defaultMessage": "Non-standard text characters are not allowed."
|
||||
@@ -371,10 +371,7 @@
|
||||
"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."
|
||||
},
|
||||
"project.text-validation.title-game-version": {
|
||||
"defaultMessage": "Project titles should not include the Minecraft version “{value}”."
|
||||
},
|
||||
"project.text-validation.title-loader": {
|
||||
"defaultMessage": "Project titles should not include the loader “{value}”."
|
||||
"project.text-validation.title-version-number": {
|
||||
"defaultMessage": "Names are not allowed to include version numbers."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ export type ProjectTextValidationCode =
|
||||
| 'text-slur'
|
||||
| 'text-profanity'
|
||||
| 'text-non-standard'
|
||||
| 'title-game-version'
|
||||
| 'title-loader'
|
||||
| 'title-version-number'
|
||||
| 'title-minecraft-branding'
|
||||
| 'summary-link'
|
||||
| 'summary-matches-title'
|
||||
@@ -68,13 +67,9 @@ const messages = defineMessages({
|
||||
id: 'project.text-validation.non-standard-text',
|
||||
defaultMessage: 'Non-standard text characters are not allowed.',
|
||||
},
|
||||
titleGameVersion: {
|
||||
id: 'project.text-validation.title-game-version',
|
||||
defaultMessage: 'Project titles should not include the Minecraft version “{value}”.',
|
||||
},
|
||||
titleLoader: {
|
||||
id: 'project.text-validation.title-loader',
|
||||
defaultMessage: 'Project titles should not include the loader “{value}”.',
|
||||
titleVersionNumber: {
|
||||
id: 'project.text-validation.title-version-number',
|
||||
defaultMessage: 'Names are not allowed to include version numbers.',
|
||||
},
|
||||
titleMinecraftBranding: {
|
||||
id: 'nags.minecraft-title-clause.description',
|
||||
@@ -126,23 +121,6 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const titleMetadataMessages = {
|
||||
'game-version': messages.titleGameVersion,
|
||||
loader: messages.titleLoader,
|
||||
}
|
||||
|
||||
export type ProjectTitleMetadataKind = 'game-version' | 'loader'
|
||||
|
||||
export interface ProjectTitleMetadata {
|
||||
gameVersions: readonly string[]
|
||||
loaders: readonly string[]
|
||||
}
|
||||
|
||||
export interface ProjectTitleMetadataMatch {
|
||||
kind: ProjectTitleMetadataKind
|
||||
value: string
|
||||
}
|
||||
|
||||
const linkify = new LinkifyIt({
|
||||
fuzzyEmail: false,
|
||||
fuzzyIP: true,
|
||||
@@ -153,28 +131,6 @@ function normalizeForSearch(value: string) {
|
||||
return value.normalize('NFC').toLowerCase()
|
||||
}
|
||||
|
||||
export function findProjectTitleMetadata(
|
||||
title: string,
|
||||
metadata: ProjectTitleMetadata,
|
||||
): ProjectTitleMetadataMatch | null {
|
||||
const normalizedTitle = normalizeForSearch(title)
|
||||
const groups: ReadonlyArray<readonly [ProjectTitleMetadataKind, readonly string[]]> = [
|
||||
['game-version', metadata.gameVersions],
|
||||
['loader', metadata.loaders],
|
||||
]
|
||||
|
||||
for (const [kind, values] of groups) {
|
||||
for (const value of values) {
|
||||
const normalizedValue = normalizeForSearch(value.trim())
|
||||
if (normalizedValue && normalizedTitle.includes(normalizedValue)) {
|
||||
return { kind, value }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function normalizeProjectFieldText(value: string) {
|
||||
return value.trim().normalize('NFC')
|
||||
}
|
||||
@@ -325,22 +281,23 @@ export function validateProjectText(
|
||||
|
||||
export function validateProjectTitle(
|
||||
text: string | null | undefined,
|
||||
metadata: ProjectTitleMetadata,
|
||||
): ProjectTextValidationResult[] {
|
||||
const results = validateProjectText(text)
|
||||
if (results.length > 0 || !text) return results
|
||||
|
||||
const match = findProjectTitleMetadata(text, metadata)
|
||||
if (match) {
|
||||
const normalizedTitle = normalizeForSearch(text)
|
||||
const disallowedVersion = [...normalizedTitle.matchAll(/\d+(?:\.\d+)+/g)].find((match) => {
|
||||
const textAfterVersion = normalizedTitle.slice((match.index ?? 0) + match[0].length)
|
||||
return !/\b(?:port|fork)\b/.test(textAfterVersion)
|
||||
})
|
||||
if (disallowedVersion) {
|
||||
results.push({
|
||||
code: match.kind === 'game-version' ? 'title-game-version' : 'title-loader',
|
||||
severity: 'warn',
|
||||
message: titleMetadataMessages[match.kind],
|
||||
values: { value: match.value },
|
||||
code: 'title-version-number',
|
||||
severity: 'error',
|
||||
message: messages.titleVersionNumber,
|
||||
})
|
||||
}
|
||||
|
||||
const normalizedTitle = normalizeProjectFieldText(text).toLowerCase()
|
||||
const wordsInTitle = normalizedTitle.split(/\s+/).filter(Boolean)
|
||||
if (normalizedTitle.includes('minecraft') && wordsInTitle.length <= 3) {
|
||||
results.push({
|
||||
|
||||
@@ -4,7 +4,6 @@ import test from 'node:test'
|
||||
import {
|
||||
containsProjectLinkOrIp,
|
||||
extractProjectLinks,
|
||||
findProjectTitleMetadata,
|
||||
projectSummaryMatchesTitle,
|
||||
validateProjectDescription,
|
||||
validateProjectSummary,
|
||||
@@ -12,24 +11,6 @@ import {
|
||||
validateProjectTitle,
|
||||
} from './index.ts'
|
||||
|
||||
const metadata = {
|
||||
gameVersions: ['1.21.1'],
|
||||
loaders: ['fabric'],
|
||||
}
|
||||
|
||||
test('finds game versions and loaders in project titles', () => {
|
||||
assert.deepEqual(findProjectTitleMetadata('Tools for 1.21.1', metadata), {
|
||||
kind: 'game-version',
|
||||
value: '1.21.1',
|
||||
})
|
||||
assert.deepEqual(findProjectTitleMetadata('FABRIC Tools', metadata), {
|
||||
kind: 'loader',
|
||||
value: 'fabric',
|
||||
})
|
||||
assert.equal(findProjectTitleMetadata('Magical Tools', metadata), null)
|
||||
assert.equal(findProjectTitleMetadata('Ordinary Tools', metadata), null)
|
||||
})
|
||||
|
||||
test('compares summaries and titles after trimming and Unicode normalization', () => {
|
||||
assert.equal(projectSummaryMatchesTitle(' Caf\u00e9 ', 'Cafe\u0301'), true)
|
||||
assert.equal(projectSummaryMatchesTitle('Project summary', 'Project title'), false)
|
||||
@@ -72,26 +53,37 @@ test('validates shared project text', () => {
|
||||
})
|
||||
|
||||
test('validates project titles', () => {
|
||||
assert.deepEqual(validateProjectTitle('Fabric Tools', metadata), [
|
||||
assert.deepEqual(validateProjectTitle('Tools 1.2.3'), [
|
||||
{
|
||||
code: 'title-loader',
|
||||
severity: 'warn',
|
||||
code: 'title-version-number',
|
||||
severity: 'error',
|
||||
message: {
|
||||
id: 'project.text-validation.title-loader',
|
||||
defaultMessage: 'Project titles should not include the loader “{value}”.',
|
||||
id: 'project.text-validation.title-version-number',
|
||||
defaultMessage: 'Names are not allowed to include version numbers.',
|
||||
},
|
||||
values: { value: 'fabric' },
|
||||
},
|
||||
])
|
||||
assert.equal(
|
||||
validateProjectTitle('Minecraft Tools', metadata)[0]?.code,
|
||||
'title-minecraft-branding',
|
||||
)
|
||||
assert.deepEqual(validateProjectTitle('Tools 1.2'), [
|
||||
{
|
||||
code: 'title-version-number',
|
||||
severity: 'error',
|
||||
message: {
|
||||
id: 'project.text-validation.title-version-number',
|
||||
defaultMessage: 'Names are not allowed to include version numbers.',
|
||||
},
|
||||
},
|
||||
])
|
||||
assert.deepEqual(validateProjectTitle('My Mod 1.2 Fabric Port'), [])
|
||||
assert.deepEqual(validateProjectTitle('My Mod 1.2 FORK'), [])
|
||||
assert.equal(validateProjectTitle('My Port of Mod 1.2')[0]?.code, 'title-version-number')
|
||||
assert.equal(validateProjectTitle('My Mod 1.2 Supported')[0]?.code, 'title-version-number')
|
||||
assert.deepEqual(validateProjectTitle('Fabric Tools'), [])
|
||||
assert.equal(validateProjectTitle('Minecraft Tools')[0]?.code, 'title-minecraft-branding')
|
||||
assert.deepEqual(
|
||||
validateProjectTitle('Minecraft Fabric Tools', metadata).map(({ code }) => code),
|
||||
['title-loader', 'title-minecraft-branding'],
|
||||
validateProjectTitle('Minecraft Tools 1.2').map(({ code }) => code),
|
||||
['title-version-number', 'title-minecraft-branding'],
|
||||
)
|
||||
assert.deepEqual(validateProjectTitle('Ordinary Tools', metadata), [])
|
||||
assert.deepEqual(validateProjectTitle('Ordinary Tools'), [])
|
||||
})
|
||||
|
||||
test('validates project summaries', () => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Labrinth } from '@modrinth/api-client'
|
||||
|
||||
import {
|
||||
type ProjectTextValidationResult,
|
||||
type ProjectTitleMetadata,
|
||||
validateProjectDescription,
|
||||
validateProjectSummary,
|
||||
validateProjectText,
|
||||
@@ -29,7 +28,6 @@ export interface ProjectValidationResult {
|
||||
|
||||
export function validateProjectFields(
|
||||
project: Labrinth.Projects.v3.Project,
|
||||
titleMetadata: ProjectTitleMetadata,
|
||||
): ProjectValidationResult {
|
||||
const failures: ProjectValidationFailure[] = []
|
||||
|
||||
@@ -47,7 +45,7 @@ export function validateProjectFields(
|
||||
)
|
||||
}
|
||||
|
||||
addFailures('name', validateProjectTitle(project.name, titleMetadata))
|
||||
addFailures('name', validateProjectTitle(project.name))
|
||||
addFailures('summary', validateProjectSummary(project.summary, project.name))
|
||||
addFailures('description', validateProjectDescription(project.description))
|
||||
|
||||
@@ -67,9 +65,6 @@ export function validateProjectFields(
|
||||
}
|
||||
}
|
||||
|
||||
export function hasProjectFieldValidationFailures(
|
||||
project: Labrinth.Projects.v3.Project,
|
||||
titleMetadata: ProjectTitleMetadata,
|
||||
): boolean {
|
||||
return !validateProjectFields(project, titleMetadata).valid
|
||||
export function hasProjectFieldValidationFailures(project: Labrinth.Projects.v3.Project): boolean {
|
||||
return !validateProjectFields(project).valid
|
||||
}
|
||||
|
||||
@@ -3,14 +3,8 @@ import test from 'node:test'
|
||||
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
|
||||
import type { ProjectTitleMetadata } from '../project-fields/index.ts'
|
||||
import { hasProjectFieldValidationFailures, validateProjectFields } from './index.ts'
|
||||
|
||||
const metadata: ProjectTitleMetadata = {
|
||||
gameVersions: ['1.21.1'],
|
||||
loaders: ['fabric'],
|
||||
}
|
||||
|
||||
function createProject(
|
||||
overrides: Partial<Labrinth.Projects.v3.Project> = {},
|
||||
): Labrinth.Projects.v3.Project {
|
||||
@@ -25,8 +19,8 @@ function createProject(
|
||||
|
||||
test('validates project fields and gallery text', () => {
|
||||
const project = createProject({
|
||||
name: 'Fabric Tools',
|
||||
summary: 'Fabric Tools',
|
||||
name: 'Ordinary Tools',
|
||||
summary: 'Ordinary Tools',
|
||||
description: '𝐀',
|
||||
gallery: [
|
||||
{
|
||||
@@ -41,7 +35,7 @@ test('validates project fields and gallery text', () => {
|
||||
],
|
||||
})
|
||||
|
||||
const result = validateProjectFields(project, metadata)
|
||||
const result = validateProjectFields(project)
|
||||
|
||||
assert.equal(result.valid, false)
|
||||
assert.deepEqual(
|
||||
@@ -52,12 +46,6 @@ test('validates project fields and gallery text', () => {
|
||||
message: message.id,
|
||||
})),
|
||||
[
|
||||
{
|
||||
field: 'name',
|
||||
galleryIndex: undefined,
|
||||
galleryUrl: undefined,
|
||||
message: 'project.text-validation.title-loader',
|
||||
},
|
||||
{
|
||||
field: 'summary',
|
||||
galleryIndex: undefined,
|
||||
@@ -93,36 +81,40 @@ test('reports whether a project has field validation failures', () => {
|
||||
const validProject = createProject()
|
||||
const invalidProject = createProject({ summary: 'This project is shit' })
|
||||
|
||||
assert.deepEqual(validateProjectFields(validProject, metadata), {
|
||||
assert.deepEqual(validateProjectFields(validProject), {
|
||||
valid: true,
|
||||
failures: [],
|
||||
})
|
||||
assert.equal(hasProjectFieldValidationFailures(validProject, metadata), false)
|
||||
assert.equal(hasProjectFieldValidationFailures(invalidProject, metadata), true)
|
||||
assert.equal(hasProjectFieldValidationFailures(validProject), false)
|
||||
assert.equal(hasProjectFieldValidationFailures(invalidProject), true)
|
||||
})
|
||||
|
||||
test('treats title metadata and summary content recommendations as warnings', () => {
|
||||
test('treats version numbers as errors and summary content recommendations as warnings', () => {
|
||||
const project = createProject({
|
||||
name: 'Fabric Tools',
|
||||
name: 'Tools 1.2.3',
|
||||
summary: 'Visit modrinth.com for more information',
|
||||
})
|
||||
const result = validateProjectFields(project, metadata)
|
||||
const result = validateProjectFields(project)
|
||||
|
||||
assert.equal(result.valid, true)
|
||||
assert.equal(result.valid, false)
|
||||
assert.deepEqual(
|
||||
result.failures.map(({ code, severity }) => ({ code, severity })),
|
||||
[
|
||||
{ code: 'title-loader', severity: 'warn' },
|
||||
{ code: 'title-version-number', severity: 'error' },
|
||||
{ code: 'summary-link', severity: 'warn' },
|
||||
],
|
||||
)
|
||||
assert.equal(hasProjectFieldValidationFailures(project, metadata), false)
|
||||
assert.equal(hasProjectFieldValidationFailures(project), true)
|
||||
assert.equal(
|
||||
hasProjectFieldValidationFailures(createProject({ name: 'Tools 1.2 Fabric Port' })),
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
test('reports summary recommendations without invalidating the project', () => {
|
||||
const project = createProject({ summary: 'Short summary' })
|
||||
|
||||
assert.deepEqual(validateProjectFields(project, metadata), {
|
||||
assert.deepEqual(validateProjectFields(project), {
|
||||
valid: true,
|
||||
failures: [
|
||||
{
|
||||
@@ -138,5 +130,5 @@ test('reports summary recommendations without invalidating the project', () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
assert.equal(hasProjectFieldValidationFailures(project, metadata), false)
|
||||
assert.equal(hasProjectFieldValidationFailures(project), false)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user