Skip to content

Commit

Permalink
add multiselect nullable changeset + add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dcousens committed Jul 9, 2024
1 parent d1dba42 commit a3c80b9
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/add-isnullable-multiselect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': minor
---

Add `db.isNullable` support for multiselect field type, defaults to false
27 changes: 12 additions & 15 deletions packages/core/src/fields/types/multiselect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
jsonFieldTypePolyfilledForSQLite,
} from '../../../types'
import { graphql } from '../../..'
import { userInputError } from '../../../lib/core/graphql-errors'
import { makeValidateHook } from '../../non-null-graphql'
import { mergeFieldHooks } from '../../resolve-hooks'

Expand All @@ -25,12 +24,12 @@ export type MultiselectFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
* If `enum` is provided on SQLite, it will use an enum in GraphQL but a string in the database.
*/
type?: 'string' | 'enum'
defaultValue?: readonly string[]
defaultValue?: readonly string[] | null
}
| {
options: readonly { label: string, value: number }[]
type: 'integer'
defaultValue?: readonly number[]
defaultValue?: readonly number[] | null
}
) & {
db?: {
Expand All @@ -48,7 +47,7 @@ export function multiselect <ListTypeInfo extends BaseListTypeInfo> (
config: MultiselectFieldConfig<ListTypeInfo>
): FieldTypeFunc<ListTypeInfo> {
const {
defaultValue = [],
defaultValue = [], // TODO: deprecated, remove in breaking change
} = config

config.db ??= {}
Expand All @@ -64,7 +63,7 @@ export function multiselect <ListTypeInfo extends BaseListTypeInfo> (
return graphql.arg({ type: nonNullList(type) })
}

const resolveCreate = <T extends string | number>(val: T[] | null | undefined): T[] => {
const resolveCreate = <T extends string | number>(val: T[] | null | undefined): T[] | null => {
const resolved = resolveUpdate(val)
if (resolved === undefined) {
return defaultValue as T[]
Expand All @@ -74,15 +73,11 @@ export function multiselect <ListTypeInfo extends BaseListTypeInfo> (

const resolveUpdate = <T extends string | number>(
val: T[] | null | undefined
): T[] | undefined => {
if (val === null) {
throw userInputError('multiselect fields cannot be set to null')
}
): T[] | null | undefined => {
return val
}

const transformedConfig = configToOptionsAndGraphQLType(config, meta)

const accepted = new Set(transformedConfig.options.map(x => x.value))
if (accepted.size !== transformedConfig.options.length) {
throw new Error(`${meta.listKey}.${meta.fieldKey} has duplicate options, this is not allowed`)
Expand All @@ -94,8 +89,8 @@ export function multiselect <ListTypeInfo extends BaseListTypeInfo> (
} = makeValidateHook(meta, config, ({ inputData, operation, addValidationError }) => {
if (operation === 'delete') return

const values: readonly (string | number)[] | undefined = inputData[meta.fieldKey] // resolvedData is JSON
if (values !== undefined) {
const values: readonly (string | number)[] | null | undefined = inputData[meta.fieldKey] // resolvedData is JSON
if (values != null) {
for (const value of values) {
if (!accepted.has(value)) {
addValidationError(`'${value}' is not an accepted option`)
Expand Down Expand Up @@ -137,7 +132,10 @@ export function multiselect <ListTypeInfo extends BaseListTypeInfo> (
mode,
map: config?.db?.map,
extendPrismaSchema: config.db?.extendPrismaSchema,
default: { kind: 'literal', value: JSON.stringify(defaultValue) },
default: {
kind: 'literal',
value: JSON.stringify(defaultValue ?? null)
},
}
)
}
Expand Down Expand Up @@ -191,5 +189,4 @@ function configToOptionsAndGraphQLType (
}
}

const nonNullList = <T extends graphql.NullableType>(type: T) =>
graphql.list(graphql.nonNull(type))
const nonNullList = <T extends graphql.NullableType>(type: T) => graphql.list(graphql.nonNull(type))
38 changes: 20 additions & 18 deletions tests/api-tests/fields/required.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,27 @@ for (const modulePath of testModules) {

const messages = [`Test.testField: missing value`]

test(
'Create an object without the required field',
runner(async ({ context }) => {
const { data, errors } = await context.graphql.raw({
query: `
mutation {
createTest(data: { name: "test entry" } ) { id }
}`,
if (!mod.hasDefaultDefault) {
test(
'Create an object without the required field',
runner(async ({ context }) => {
const { data, errors } = await context.graphql.raw({
query: `
mutation {
createTest(data: { name: "test entry" } ) { id }
}`,
})
expect(data).toEqual({ createTest: null })
expectValidationError(errors, [
{
path: ['createTest'],
messages:
mod.name === 'Text' ? ['Test.testField: value must not be empty'] : messages,
},
])
})
expect(data).toEqual({ createTest: null })
expectValidationError(errors, [
{
path: ['createTest'],
messages:
mod.name === 'Text' ? ['Test.testField: value must not be empty'] : messages,
},
])
})
)
)
}

test(
'Create an object with an explicit null value',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ export const exampleValue2 = (matrixValue: MatrixValue) =>
? ['a string', '1number']
: [2, 4]
export const supportsNullInput = false
export const neverNull = true
export const hasDefaultDefault = true
export const neverNull = false
export const supportsUnique = false
export const supportsDbMap = true
export const skipRequiredTest = true
export const skipRequiredTest = false
export const fieldConfig = (matrixValue: MatrixValue) => {
if (matrixValue === 'enum' || matrixValue === 'string') {
return {
Expand Down

0 comments on commit a3c80b9

Please sign in to comment.