From f6638e4575e0ab25eae18b92489544dfdc3f1f2c Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Sat, 30 Dec 2023 12:59:47 -0500 Subject: [PATCH] fix: Support Custom Id Field Names in when generating Cells (#9778) Co-authored-by: Tobbe Lundberg --- __fixtures__/test-project/web/src/App.tsx | 10 +- .../web/src/graphql/possibleTypes.ts | 11 + .../__tests__/__snapshots__/cell.test.js.snap | 234 ++++++++++++++++++ .../generate/cell/__tests__/cell.test.js | 115 +++++++++ .../cell/__tests__/fixtures/schema.prisma | 5 + .../cli/src/commands/generate/cell/cell.js | 5 + .../generate/cell/templates/cell.tsx.template | 4 +- .../cell/templates/cellList.tsx.template | 4 +- .../generate/cell/templates/mock.js.template | 2 +- .../cell/templates/mockList.js.template | 6 +- .../src/commands/generate/cell/utils/utils.js | 3 + 11 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 __fixtures__/test-project/web/src/graphql/possibleTypes.ts diff --git a/__fixtures__/test-project/web/src/App.tsx b/__fixtures__/test-project/web/src/App.tsx index 65419d60c7d6..cb77cb1e4322 100644 --- a/__fixtures__/test-project/web/src/App.tsx +++ b/__fixtures__/test-project/web/src/App.tsx @@ -1,6 +1,7 @@ import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' +import possibleTypes from 'src/graphql/possibleTypes' import FatalErrorPage from 'src/pages/FatalErrorPage' import Routes from 'src/Routes' @@ -13,7 +14,14 @@ const App = () => ( - + diff --git a/__fixtures__/test-project/web/src/graphql/possibleTypes.ts b/__fixtures__/test-project/web/src/graphql/possibleTypes.ts new file mode 100644 index 000000000000..a8d476e9029c --- /dev/null +++ b/__fixtures__/test-project/web/src/graphql/possibleTypes.ts @@ -0,0 +1,11 @@ +export interface PossibleTypesResultData { + possibleTypes: { + [key: string]: string[] + } +} + +const result: PossibleTypesResultData = { + possibleTypes: {}, +} + +export default result diff --git a/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap b/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap index bce33bd25656..4767a23a590b 100644 --- a/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap +++ b/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap @@ -52,6 +52,240 @@ export const Success = ({ equipment }) => { " `; +exports[`Custom Id Field files List cell creates a cell list component with a custom id field 1`] = ` +"export const QUERY = gql\` + query CustomIdFieldsQuery { + customIdFields { + uuid + } + } +\` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Empty
+ +export const Failure = ({ error }) => ( +
Error: {error?.message}
+) + +export const Success = ({ customIdFields }) => { + return ( +
    + {customIdFields.map((item) => { + return
  • {JSON.stringify(item)}
  • + })} +
+ ) +} +" +`; + +exports[`Custom Id Field files List cell creates a cell list mock with a custom id field 1`] = ` +"// Define your own mock data here: +export const standard = (/* vars, { ctx, req } */) => ({ + customIdFields: [{ uuid: '42' }, { uuid: '43' }, { uuid: '44' }], +}) +" +`; + +exports[`Custom Id Field files List cell creates a cell list stories with a custom id field 1`] = ` +"import { Loading, Empty, Failure, Success } from './CustomIdFieldsCell' +import { standard } from './CustomIdFieldsCell.mock' + +const meta = { + title: 'Cells/CustomIdFieldsCell', + tags: ['autodocs'], +} + +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, +} + +export const empty = { + render: () => { + return Empty ? : <> + }, +} + +export const failure = { + render: (args) => { + return Failure ? : <> + }, +} + +export const success = { + render: (args) => { + return Success ? : <> + }, +} +" +`; + +exports[`Custom Id Field files List cell creates a cell list test with a custom id field 1`] = ` +"import { render } from '@redwoodjs/testing/web' +import { Loading, Empty, Failure, Success } from './CustomIdFieldsCell' +import { standard } from './CustomIdFieldsCell.mock' + +// Generated boilerplate tests do not account for all circumstances +// and can fail without adjustments, e.g. Float and DateTime types. +// Please refer to the RedwoodJS Testing Docs: +// https://redwoodjs.com/docs/testing#testing-cells +// https://redwoodjs.com/docs/testing#jest-expect-type-considerations + +describe('CustomIdFieldsCell', () => { + it('renders Loading successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Empty successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Failure successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + // When you're ready to test the actual output of your component render + // you could test that, for example, certain text is present: + // + // 1. import { screen } from '@redwoodjs/testing/web' + // 2. Add test: expect(screen.getByText('Hello, world')).toBeInTheDocument() + + it('renders Success successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) +}) +" +`; + +exports[`Custom Id Field files Single cell creates a cell component with a custom id field 1`] = ` +"export const QUERY = gql\` + query FindCustomIdFieldQuery($id: String!) { + customIdField: customIdField(uuid: $id) { + uuid + } + } +\` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Empty
+ +export const Failure = ({ error }) => ( +
Error: {error?.message}
+) + +export const Success = ({ customIdField }) => { + return
{JSON.stringify(customIdField)}
+} +" +`; + +exports[`Custom Id Field files Single cell creates a cell mock with a custom id field 1`] = ` +"// Define your own mock data here: +export const standard = (/* vars, { ctx, req } */) => ({ + customIdField: { + uuid: '42', + }, +}) +" +`; + +exports[`Custom Id Field files Single cell creates a cell stories with a custom id field 1`] = ` +"import { Loading, Empty, Failure, Success } from './CustomIdFieldCell' +import { standard } from './CustomIdFieldCell.mock' + +const meta = { + title: 'Cells/CustomIdFieldCell', + tags: ['autodocs'], +} + +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, +} + +export const empty = { + render: () => { + return Empty ? : <> + }, +} + +export const failure = { + render: (args) => { + return Failure ? : <> + }, +} + +export const success = { + render: (args) => { + return Success ? : <> + }, +} +" +`; + +exports[`Custom Id Field files Single cell creates a cell test with a custom id field 1`] = ` +"import { render } from '@redwoodjs/testing/web' +import { Loading, Empty, Failure, Success } from './CustomIdFieldCell' +import { standard } from './CustomIdFieldCell.mock' + +// Generated boilerplate tests do not account for all circumstances +// and can fail without adjustments, e.g. Float and DateTime types. +// Please refer to the RedwoodJS Testing Docs: +// https://redwoodjs.com/docs/testing#testing-cells +// https://redwoodjs.com/docs/testing#jest-expect-type-considerations + +describe('CustomIdFieldCell', () => { + it('renders Loading successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Empty successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Failure successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + // When you're ready to test the actual output of your component render + // you could test that, for example, certain text is present: + // + // 1. import { screen } from '@redwoodjs/testing/web' + // 2. Add test: expect(screen.getByText('Hello, world')).toBeInTheDocument() + + it('renders Success successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) +}) +" +`; + exports[`Kebab case words creates a cell component with a kebabCase word name 1`] = ` "export const QUERY = gql\` query FindUserProfileQuery($id: Int!) { diff --git a/packages/cli/src/commands/generate/cell/__tests__/cell.test.js b/packages/cli/src/commands/generate/cell/__tests__/cell.test.js index a52684c7280e..cd72445c048b 100644 --- a/packages/cli/src/commands/generate/cell/__tests__/cell.test.js +++ b/packages/cli/src/commands/generate/cell/__tests__/cell.test.js @@ -686,3 +686,118 @@ describe('Custom query names', () => { ) }) }) + +describe('Custom Id Field files', () => { + let customIdFieldFiles + let customIdFieldListFiles + + describe('Single cell', () => { + beforeAll(async () => { + customIdFieldFiles = await cell.files({ + name: 'CustomIdField', + tests: true, + stories: true, + list: false, + }) + }) + + it('returns exactly 4 files', () => { + expect(Object.keys(customIdFieldFiles).length).toEqual(4) + }) + + it('creates a cell component with a custom id field', () => { + expect( + customIdFieldFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldCell/CustomIdFieldCell.jsx' + ) + ] + ).toMatchSnapshot() + }) + + it('creates a cell test with a custom id field', () => { + expect( + customIdFieldFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldCell/CustomIdFieldCell.test.jsx' + ) + ] + ).toMatchSnapshot() + }) + + it('creates a cell stories with a custom id field', () => { + expect( + customIdFieldFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldCell/CustomIdFieldCell.stories.jsx' + ) + ] + ).toMatchSnapshot() + }) + + it('creates a cell mock with a custom id field', () => { + expect( + customIdFieldFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldCell/CustomIdFieldCell.mock.js' + ) + ] + ).toMatchSnapshot() + }) + }) + + describe('List cell', () => { + beforeAll(async () => { + customIdFieldListFiles = await cell.files({ + name: 'CustomIdField', + tests: true, + stories: true, + list: true, + }) + }) + + it('returns exactly 4 files', () => { + expect(Object.keys(customIdFieldFiles).length).toEqual(4) + }) + + it('creates a cell list component with a custom id field', () => { + expect( + customIdFieldListFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldsCell/CustomIdFieldsCell.jsx' + ) + ] + ).toMatchSnapshot() + }) + + it('creates a cell list test with a custom id field', () => { + expect( + customIdFieldListFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldsCell/CustomIdFieldsCell.test.jsx' + ) + ] + ).toMatchSnapshot() + }) + + it('creates a cell list stories with a custom id field', () => { + expect( + customIdFieldListFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldsCell/CustomIdFieldsCell.stories.jsx' + ) + ] + ).toMatchSnapshot() + }) + + it('creates a cell list mock with a custom id field', () => { + expect( + customIdFieldListFiles[ + path.normalize( + '/path/to/project/web/src/components/CustomIdFieldsCell/CustomIdFieldsCell.mock.js' + ) + ] + ).toMatchSnapshot() + }) + }) +}) diff --git a/packages/cli/src/commands/generate/cell/__tests__/fixtures/schema.prisma b/packages/cli/src/commands/generate/cell/__tests__/fixtures/schema.prisma index 6946cb17bbbb..30eb90bdce41 100644 --- a/packages/cli/src/commands/generate/cell/__tests__/fixtures/schema.prisma +++ b/packages/cli/src/commands/generate/cell/__tests__/fixtures/schema.prisma @@ -30,3 +30,8 @@ model Address { city String country String } + +model CustomIdField { + uuid String @id @default(uuid()) + name String +} diff --git a/packages/cli/src/commands/generate/cell/cell.js b/packages/cli/src/commands/generate/cell/cell.js index 00342139444a..e54fb95fd737 100644 --- a/packages/cli/src/commands/generate/cell/cell.js +++ b/packages/cli/src/commands/generate/cell/cell.js @@ -17,6 +17,7 @@ import { import { checkProjectForQueryField, + getIdName, getIdType, operationNameIsUnique, uniqueOperationName, @@ -27,6 +28,7 @@ const REDWOOD_WEB_PATH_NAME = 'components' export const files = async ({ name, typescript, ...options }) => { let cellName = removeGeneratorName(name, 'cell') + let idName = 'id' let idType, mockIdValues = [42, 43, 44], model = null @@ -41,6 +43,7 @@ export const files = async ({ name, typescript, ...options }) => { // needed for the singular cell GQL query find by id case try { model = await getSchema(pascalcase(singularize(cellName))) + idName = getIdName(model) idType = getIdType(model) mockIdValues = idType === 'String' @@ -84,6 +87,7 @@ export const files = async ({ name, typescript, ...options }) => { templatePath: `cell${templateNameSuffix}.tsx.template`, templateVars: { operationName, + idName, idType, }, }) @@ -114,6 +118,7 @@ export const files = async ({ name, typescript, ...options }) => { generator: 'cell', templatePath: `mock${templateNameSuffix}.js.template`, templateVars: { + idName, mockIdValues, }, }) diff --git a/packages/cli/src/commands/generate/cell/templates/cell.tsx.template b/packages/cli/src/commands/generate/cell/templates/cell.tsx.template index 264ef6031ec9..e264c97d8488 100644 --- a/packages/cli/src/commands/generate/cell/templates/cell.tsx.template +++ b/packages/cli/src/commands/generate/cell/templates/cell.tsx.template @@ -4,8 +4,8 @@ import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' export const QUERY = gql` query ${operationName}($id: ${idType}!) { - ${camelName}: ${camelName}(id: $id) { - id + ${camelName}: ${camelName}(${idName}: $id) { + ${idName} } } ` diff --git a/packages/cli/src/commands/generate/cell/templates/cellList.tsx.template b/packages/cli/src/commands/generate/cell/templates/cellList.tsx.template index c4d434893132..e6fba57b75f4 100644 --- a/packages/cli/src/commands/generate/cell/templates/cellList.tsx.template +++ b/packages/cli/src/commands/generate/cell/templates/cellList.tsx.template @@ -6,7 +6,7 @@ import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' export const QUERY = gql` query ${operationName} { ${camelName} { - id + ${idName} } } ` @@ -23,7 +23,7 @@ export const Success = ({ ${camelName} }: CellSuccessProps<${operationName}>) => return (
    {${camelName}.map((item) => { - return
  • {JSON.stringify(item)}
  • + return
  • {JSON.stringify(item)}
  • })}
) diff --git a/packages/cli/src/commands/generate/cell/templates/mock.js.template b/packages/cli/src/commands/generate/cell/templates/mock.js.template index b96dd185377a..63db0e4616cf 100644 --- a/packages/cli/src/commands/generate/cell/templates/mock.js.template +++ b/packages/cli/src/commands/generate/cell/templates/mock.js.template @@ -1,6 +1,6 @@ // Define your own mock data here: export const standard = (/* vars, { ctx, req } */) => ({ ${camelName}: { - id: ${mockIdValues[0]} + ${idName}: ${mockIdValues[0]} } }) diff --git a/packages/cli/src/commands/generate/cell/templates/mockList.js.template b/packages/cli/src/commands/generate/cell/templates/mockList.js.template index 9ed5cafed329..237dc4a8ae46 100644 --- a/packages/cli/src/commands/generate/cell/templates/mockList.js.template +++ b/packages/cli/src/commands/generate/cell/templates/mockList.js.template @@ -1,8 +1,8 @@ // Define your own mock data here: export const standard = (/* vars, { ctx, req } */) => ({ ${camelName}: [ - { id: ${mockIdValues[0]} }, - { id: ${mockIdValues[1]} }, - { id: ${mockIdValues[2]} } + { ${idName}: ${mockIdValues[0]} }, + { ${idName}: ${mockIdValues[1]} }, + { ${idName}: ${mockIdValues[2]} } ] }) diff --git a/packages/cli/src/commands/generate/cell/utils/utils.js b/packages/cli/src/commands/generate/cell/utils/utils.js index 347bcd0ebfe4..efca1d8f5bcd 100644 --- a/packages/cli/src/commands/generate/cell/utils/utils.js +++ b/packages/cli/src/commands/generate/cell/utils/utils.js @@ -43,6 +43,9 @@ export const getIdType = (model) => { return model.fields.find((field) => field.isId)?.type } +export const getIdName = (model) => { + return model.fields.find((field) => field.isId)?.name +} /** * * This function checks the project for the field name supplied,