diff --git a/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js b/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js index b14886bfd062..6d2604dc05eb 100644 --- a/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js +++ b/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js @@ -1,5 +1,21 @@ global.__dirname = __dirname + +import fs from 'fs' + +import 'src/lib/test' +import { getPaths, getDefaultArgs } from 'src/lib' + +import { defaults, files } from '../../../generate/scaffold/scaffold' +import { tasks } from '../scaffold' + jest.mock('fs') +jest.mock('@babel/core', () => { + return { + transform: () => ({ + code: '', + }), + } +}) jest.mock('src/lib', () => { const path = require('path') return { @@ -10,115 +26,203 @@ jest.mock('src/lib', () => { } }) -import fs from 'fs' +describe('rw destroy scaffold', () => { + describe('destroy scaffold post', () => { + beforeEach(async () => { + fs.__setMockFiles({ + ...(await files({ ...getDefaultArgs(defaults), model: 'Post' })), + [getPaths().web.routes]: [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'), + }) + }) -import 'src/lib/test' -import { getPaths } from 'src/lib' + afterEach(() => { + fs.__setMockFiles({}) + jest.spyOn(fs, 'unlinkSync').mockClear() + }) -import { files } from '../../../generate/scaffold/scaffold' -import { tasks } from '../scaffold' + test('destroys files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ model: 'Post' }) + t.setRenderer('silent') -describe('destroy scaffold post', () => { - beforeEach(async () => { - fs.__setMockFiles({ - ...(await files({ model: 'Post' })), - [getPaths().web.routes]: [ - '', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '', - ].join('\n'), + return t._tasks[0].run().then(async () => { + const generatedFiles = Object.keys( + await files({ ...getDefaultArgs(defaults), model: 'Post' }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + }) }) - }) - afterEach(() => { - fs.__setMockFiles({}) - jest.spyOn(fs, 'unlinkSync').mockClear() - }) + describe('for typescript files', () => { + beforeEach(async () => { + fs.__setMockFiles({}) // clear filesystem so files call works as expected + fs.__setMockFiles({ + ...(await files({ + ...getDefaultArgs(defaults), + typescript: true, + model: 'Post', + })), + [getPaths().web.routes]: [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'), + }) + }) - test('destroys files', async () => { - const unlinkSpy = jest.spyOn(fs, 'unlinkSync') - const t = tasks({ model: 'Post' }) - t.setRenderer('silent') + test('destroys files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ model: 'Post' }) + t.setRenderer('silent') - return t._tasks[0].run().then(async () => { - const generatedFiles = Object.keys(await files({ model: 'Post' })) - expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) - generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + return t._tasks[0].run().then(async () => { + const generatedFiles = Object.keys( + await files({ + ...getDefaultArgs(defaults), + typescript: true, + model: 'Post', + }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => + expect(unlinkSpy).toHaveBeenCalledWith(f) + ) + }) + }) }) - }) - test('cleans up routes from Routes.js', async () => { - const t = tasks({ model: 'Post' }) - t.setRenderer('silent') + test('cleans up routes from Routes.js', async () => { + const t = tasks({ model: 'Post' }) + t.setRenderer('silent') - return t._tasks[1].run().then(() => { - const routes = fs.readFileSync(getPaths().web.routes) - expect(routes).toEqual( - [ + return t._tasks[1].run().then(() => { + const routes = fs.readFileSync(getPaths().web.routes) + expect(routes).toEqual( + [ + '', + ' ', + ' ', + '', + ].join('\n') + ) + }) + }) + }) + + describe('destroy namespaced scaffold post', () => { + beforeEach(async () => { + fs.__setMockFiles({ + ...(await files({ + ...getDefaultArgs(defaults), + model: 'Post', + path: 'admin', + })), + [getPaths().web.routes]: [ '', + ' ', + ' ', + ' ', ' ', ' ', '', - ].join('\n') - ) + ].join('\n'), + }) }) - }) -}) -describe('destroy namespaced scaffold post', () => { - beforeEach(async () => { - fs.__setMockFiles({ - ...(await files({ model: 'Post', path: 'admin' })), - [getPaths().web.routes]: [ - '', - ' ', - ' ', - ' ', - ' ', - ' ', - '', - ].join('\n'), + afterEach(() => { + fs.__setMockFiles({}) + jest.spyOn(fs, 'unlinkSync').mockClear() }) - }) - afterEach(() => { - fs.__setMockFiles({}) - jest.spyOn(fs, 'unlinkSync').mockClear() - }) + test('destroys files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ model: 'Post', path: 'admin' }) + t.setRenderer('silent') - test('destroys files', async () => { - const unlinkSpy = jest.spyOn(fs, 'unlinkSync') - const t = tasks({ model: 'Post', path: 'admin' }) - t.setRenderer('silent') - - return t._tasks[0].run().then(async () => { - const generatedFiles = Object.keys( - await files({ model: 'Post', path: 'admin' }) - ) - expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) - generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + return t._tasks[0].run().then(async () => { + const generatedFiles = Object.keys( + await files({ + ...getDefaultArgs(defaults), + model: 'Post', + path: 'admin', + }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + }) }) - }) - test('cleans up routes from Routes.js', async () => { - const t = tasks({ model: 'Post', path: 'admin' }) - t.setRenderer('silent') + describe('for typescript files', () => { + beforeEach(async () => { + fs.__setMockFiles({}) // clear filesystem so files call works as expected + fs.__setMockFiles({ + ...(await files({ + ...getDefaultArgs(defaults), + model: 'Post', + path: 'admin', + })), + [getPaths().web.routes]: [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'), + }) + }) + test('destroys files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ model: 'Post', path: 'admin' }) + t.setRenderer('silent') - return t._tasks[1].run().then(() => { - const routes = fs.readFileSync(getPaths().web.routes) - expect(routes).toEqual( - [ - '', - ' ', - ' ', - '', - ].join('\n') - ) + return t._tasks[0].run().then(async () => { + const generatedFiles = Object.keys( + await files({ + ...getDefaultArgs(defaults), + model: 'Post', + path: 'admin', + }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => + expect(unlinkSpy).toHaveBeenCalledWith(f) + ) + }) + }) + }) + + test('cleans up routes from Routes.js', async () => { + const t = tasks({ model: 'Post', path: 'admin' }) + t.setRenderer('silent') + + return t._tasks[1].run().then(() => { + const routes = fs.readFileSync(getPaths().web.routes) + expect(routes).toEqual( + [ + '', + ' ', + ' ', + '', + ].join('\n') + ) + }) }) }) }) diff --git a/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js b/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js index e45ffc87192e..381114a27d0f 100644 --- a/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js +++ b/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js @@ -1,5 +1,22 @@ global.__dirname = __dirname + +import fs from 'fs' + +import 'src/lib/test' + +import { getDefaultArgs } from 'src/lib' + +import { builder, files } from '../../../generate/sdl/sdl' +import { tasks } from '../sdl' + jest.mock('fs') +jest.mock('@babel/core', () => { + return { + transform: () => ({ + code: '', + }), + } +}) jest.mock('src/lib', () => { const path = require('path') return { @@ -10,30 +27,61 @@ jest.mock('src/lib', () => { } }) -import fs from 'fs' +describe('rw destory sdl', () => { + afterEach(() => { + fs.__setMockFiles({}) + jest.spyOn(fs, 'unlinkSync').mockClear() + }) -import 'src/lib/test' + describe('for javascipt files', () => { + beforeEach(async () => { + fs.__setMockFiles( + await files({ ...getDefaultArgs(builder), name: 'Post' }) + ) + }) -import { files } from '../../../generate/sdl/sdl' -import { tasks } from '../sdl' + test('destroys sdl files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ model: 'Post' }) + t.setRenderer('silent') -beforeEach(async () => { - fs.__setMockFiles(await files({ name: 'Post' })) -}) + return t._tasks[0].run().then(async () => { + const generatedFiles = Object.keys( + await files({ ...getDefaultArgs(builder), name: 'Post' }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + }) + }) + }) -afterEach(() => { - fs.__setMockFiles({}) - jest.spyOn(fs, 'unlinkSync').mockClear() -}) + describe('for typescript files', () => { + beforeEach(async () => { + fs.__setMockFiles( + await files({ + ...getDefaultArgs(builder), + typescript: true, + name: 'Post', + }) + ) + }) -test('destroys sdl files', async () => { - const unlinkSpy = jest.spyOn(fs, 'unlinkSync') - const t = tasks({ model: 'Post' }) - t.setRenderer('silent') + test('destroys sdl files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ model: 'Post' }) + t.setRenderer('silent') - return t._tasks[0].run().then(async () => { - const generatedFiles = Object.keys(await files({ name: 'Post' })) - expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) - generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + return t._tasks[0].run().then(async () => { + const generatedFiles = Object.keys( + await files({ + ...getDefaultArgs(builder), + typescript: true, + name: 'Post', + }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + }) + }) }) }) diff --git a/packages/cli/src/commands/destroy/sdl/sdl.js b/packages/cli/src/commands/destroy/sdl/sdl.js index 4e15556be70c..5fba80c78362 100644 --- a/packages/cli/src/commands/destroy/sdl/sdl.js +++ b/packages/cli/src/commands/destroy/sdl/sdl.js @@ -14,7 +14,7 @@ export const tasks = ({ model }) => { title: 'Destroying GraphQL schema and service object files...', task: async () => { - const f = await files({ name: model, crud: false }) + const f = await files({ name: model }) return deleteFilesTask(f) }, }, diff --git a/packages/cli/src/commands/destroy/service/__tests__/service.test.js b/packages/cli/src/commands/destroy/service/__tests__/service.test.js index 2503a33673e1..0278784adfee 100644 --- a/packages/cli/src/commands/destroy/service/__tests__/service.test.js +++ b/packages/cli/src/commands/destroy/service/__tests__/service.test.js @@ -1,5 +1,22 @@ global.__dirname = __dirname + +import fs from 'fs' + +import 'src/lib/test' + +import { getDefaultArgs } from 'src/lib' + +import { builder, files } from '../../../generate/service/service' +import { tasks } from '../service' + jest.mock('fs') +jest.mock('@babel/core', () => { + return { + transform: () => ({ + code: '', + }), + } +}) jest.mock('src/lib', () => { return { ...require.requireActual('src/lib'), @@ -7,30 +24,68 @@ jest.mock('src/lib', () => { } }) -import fs from 'fs' +describe('rw destory service', () => { + afterEach(() => { + fs.__setMockFiles({}) + jest.spyOn(fs, 'unlinkSync').mockClear() + }) -import 'src/lib/test' + describe('for javascript files', () => { + beforeEach(async () => { + fs.__setMockFiles( + await files({ ...getDefaultArgs(builder), name: 'User' }) + ) + }) + test('destroys service files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ + componentName: 'service', + filesFn: files, + name: 'User', + }) + t.setRenderer('silent') -import { files } from '../../../generate/service/service' -import { tasks } from '../service' - -beforeEach(async () => { - fs.__setMockFiles(await files({ name: 'User' })) -}) + return t.run().then(async () => { + const generatedFiles = Object.keys( + await files({ ...getDefaultArgs(builder), name: 'User' }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + }) + }) + }) -afterEach(() => { - fs.__setMockFiles({}) - jest.spyOn(fs, 'unlinkSync').mockClear() -}) + describe('for typescript files', () => { + beforeEach(async () => { + fs.__setMockFiles( + await files({ + ...getDefaultArgs(builder), + typescript: true, + name: 'User', + }) + ) + }) -test('destroys service files', async () => { - const unlinkSpy = jest.spyOn(fs, 'unlinkSync') - const t = tasks({ componentName: 'service', filesFn: files, name: 'User' }) - t.setRenderer('silent') + test('destroys service files', async () => { + const unlinkSpy = jest.spyOn(fs, 'unlinkSync') + const t = tasks({ + componentName: 'service', + filesFn: files, + name: 'User', + }) + t.setRenderer('silent') - return t.run().then(async () => { - const generatedFiles = Object.keys(await files({ name: 'User' })) - expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) - generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + return t.run().then(async () => { + const generatedFiles = Object.keys( + await files({ + ...getDefaultArgs(builder), + typescript: true, + name: 'User', + }) + ) + expect(generatedFiles.length).toEqual(unlinkSpy.mock.calls.length) + generatedFiles.forEach((f) => expect(unlinkSpy).toHaveBeenCalledWith(f)) + }) + }) }) }) diff --git a/packages/cli/src/commands/destroy/service/service.js b/packages/cli/src/commands/destroy/service/service.js index bf23acadcf32..32cee3c7d345 100644 --- a/packages/cli/src/commands/destroy/service/service.js +++ b/packages/cli/src/commands/destroy/service/service.js @@ -1,4 +1,6 @@ -import { files } from '../../generate/service/service' +import { getDefaultArgs } from 'src/lib' + +import { builder, files } from '../../generate/service/service' import { createYargsForComponentDestroy } from '../helpers' // This function wraps files(), so we can pass templateVars. templateVars @@ -16,6 +18,6 @@ export const filesWithTemplateVars = (templateVars) => { export const { command, desc, handler, tasks } = createYargsForComponentDestroy( { componentName: 'service', - filesFn: filesWithTemplateVars({ crud: false }), + filesFn: filesWithTemplateVars(getDefaultArgs(builder)), } ) diff --git a/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js b/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js index ba6c85bfef84..5238145feb2d 100644 --- a/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js +++ b/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js @@ -24,9 +24,7 @@ describe('UserCell', () => { }) it('Success renders successfully', () => { - render( - - ) + render() expect( screen.queryByText('{"user":{"objectKey":"objectValue"}}') ).toBeInTheDocument() diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.js index 7ac71a1a0daf..388ade1fdbbf 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.js @@ -62,19 +62,20 @@ export const pathName = (path, name) => { export const createYargsForComponentGeneration = ({ componentName, filesFn, + builder = { force: { type: 'boolean', default: false } }, }) => { return { command: `${componentName} `, desc: `Generate a ${componentName} component.`, - builder: { force: { type: 'boolean', default: false } }, - handler: async ({ force, ...rest }) => { + builder, + handler: async (options) => { const tasks = new Listr( [ { title: `Generating ${componentName} files...`, task: async () => { - const f = await filesFn(rest) - return writeFilesTask(f, { overwriteExisting: force }) + const f = await filesFn(options) + return writeFilesTask(f, { overwriteExisting: options.force }) }, }, ], diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/assets/scaffold.css b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/assets/scaffold.css index 88320a9d5780..b2d02eda61a2 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/assets/scaffold.css +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/assets/scaffold.css @@ -2,76 +2,296 @@ normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ -.rw-scaffold *,.rw-scaffold ::after,.rw-scaffold ::before{box-sizing:inherit;border-width:0;border-style:solid;border-color:#e2e8f0} -.rw-scaffold main{display:block} -.rw-scaffold h1,.rw-scaffold h2{margin:0} -.rw-scaffold a{background-color:transparent} -.rw-scaffold ul{margin:0;padding:0} -.rw-scaffold input{font-family:inherit;font-size:100%;overflow:visible} -.rw-scaffold input:-ms-input-placeholder{color:#a0aec0} -.rw-scaffold input::-ms-input-placeholder{color:#a0aec0} -.rw-scaffold input::placeholder{color:#a0aec0} -.rw-scaffold table{border-collapse:collapse} +.rw-scaffold *, +.rw-scaffold ::after, +.rw-scaffold ::before { + box-sizing: inherit; + border-width: 0; + border-style: solid; + border-color: #e2e8f0; +} +.rw-scaffold main { + display: block; +} +.rw-scaffold h1, +.rw-scaffold h2 { + margin: 0; +} +.rw-scaffold a { + background-color: transparent; +} +.rw-scaffold ul { + margin: 0; + padding: 0; +} +.rw-scaffold input { + font-family: inherit; + font-size: 100%; + overflow: visible; +} +.rw-scaffold input:-ms-input-placeholder { + color: #a0aec0; +} +.rw-scaffold input::-ms-input-placeholder { + color: #a0aec0; +} +.rw-scaffold input::placeholder { + color: #a0aec0; +} +.rw-scaffold table { + border-collapse: collapse; +} /* Style */ -.rw-scaffold{background-color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"} -.rw-header{display:flex;justify-content:space-between;padding: 1rem 2rem 1rem 2rem} -.rw-main{margin-left:1rem;margin-right:1rem;padding-bottom:1rem} -.rw-segment{background-color:#fff;border-width:1px;border-radius:.5rem;overflow:hidden} -.rw-segment-header{background-color:#e2e8f0;color:#4a5568;padding:.75rem 1rem} -.rw-segment-main{background-color:#f7fafc;padding: 1rem} -.rw-link {color:#4299e1;text-decoration:underline} -.rw-link:hover {color:#2b6cb0} -.rw-heading {font-weight:600} -.rw-heading.rw-heading-primary{font-size:1.25rem} -.rw-heading.rw-heading-secondary{font-size:.875rem} -.rw-heading .rw-link{color:#4a5568;text-decoration: none} -.rw-heading .rw-link:hover{color:#1a202c;text-decoration:underline} -.rw-form-wrapper{box-sizing:border-box;font-size:.875rem;margin-top:-1rem} -.rw-form-error-wrapper{padding:1rem;background-color:#fff5f5;color:#c53030;border-width:1px;border-color:#feb2b2;border-radius:.25rem;margin:1rem 0} -.rw-form-error-title{margin-top:0;margin-bottom:0;font-weight:600} -.rw-form-error-list{margin-top:.5rem;list-style-type:disc;list-style-position:inside} -.rw-button{color:#718096;cursor:pointer;display:flex;justify-content:center;font-size:.75rem;font-weight:600;padding:.25rem 1rem;text-transform:uppercase;text-decoration: none;letter-spacing:.025em;border-radius:.25rem;line-height: 2} -.rw-button:hover{background-color:#718096;color:#fff} -.rw-button.rw-button-small{font-size:.75rem;border-radius:.125rem;padding:.25rem .5rem;line-height:inherit} -.rw-button.rw-button-green{background-color:#48bb78;color:#fff} -.rw-button.rw-button-green:hover{background-color:#38a169;color:#fff} -.rw-button.rw-button-blue{background-color:#3182ce;color:#fff} -.rw-button.rw-button-blue:hover{background-color:#2b6cb0} -.rw-button.rw-button-red{background-color:#e53e3e;color:#fff} -.rw-button.rw-button-red:hover{background-color:#c53030} -.rw-button-icon{font-size:1.25rem;line-height:1;margin-right:.25rem} -.rw-button-group {display:flex;justify-content:center;margin:.75rem .5rem} -.rw-button-group .rw-button { margin: 0 .25rem} -.rw-form-wrapper .rw-button-group{margin-top:2rem;margin-bottom:0} -.rw-label{display:block;margin-top:1.5rem;color:#4a5568;font-weight:600} -.rw-label.rw-label-error{color:#c53030} -.rw-input{display:block;margin-top:.5rem;width:100%;padding:.5rem;border-width:1px;border-color:#e2e8f0;color:#4a5568;border-radius:.25rem;outline:none} -.rw-input:focus{border-color:#a0aec0} -.rw-input-error{border-color:#c53030;color:#c53030} -.rw-field-error{display:block;margin-top:.25rem;font-weight:600;text-transform:uppercase;font-size:.75rem;color:#c53030} -.rw-table-wrapper-responsive{overflow-x: scroll} -.rw-table-wrapper-responsive .rw-table{min-width:48rem} -.rw-table{table-layout:auto;width:100%;font-size:.875rem} +.rw-scaffold { + background-color: #fff; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; +} +.rw-header { + display: flex; + justify-content: space-between; + padding: 1rem 2rem 1rem 2rem; +} +.rw-main { + margin-left: 1rem; + margin-right: 1rem; + padding-bottom: 1rem; +} +.rw-segment { + background-color: #fff; + border-width: 1px; + border-radius: 0.5rem; + overflow: hidden; +} +.rw-segment-header { + background-color: #e2e8f0; + color: #4a5568; + padding: 0.75rem 1rem; +} +.rw-segment-main { + background-color: #f7fafc; + padding: 1rem; +} +.rw-link { + color: #4299e1; + text-decoration: underline; +} +.rw-link:hover { + color: #2b6cb0; +} +.rw-heading { + font-weight: 600; +} +.rw-heading.rw-heading-primary { + font-size: 1.25rem; +} +.rw-heading.rw-heading-secondary { + font-size: 0.875rem; +} +.rw-heading .rw-link { + color: #4a5568; + text-decoration: none; +} +.rw-heading .rw-link:hover { + color: #1a202c; + text-decoration: underline; +} +.rw-form-wrapper { + box-sizing: border-box; + font-size: 0.875rem; + margin-top: -1rem; +} +.rw-form-error-wrapper { + padding: 1rem; + background-color: #fff5f5; + color: #c53030; + border-width: 1px; + border-color: #feb2b2; + border-radius: 0.25rem; + margin: 1rem 0; +} +.rw-form-error-title { + margin-top: 0; + margin-bottom: 0; + font-weight: 600; +} +.rw-form-error-list { + margin-top: 0.5rem; + list-style-type: disc; + list-style-position: inside; +} +.rw-button { + color: #718096; + cursor: pointer; + display: flex; + justify-content: center; + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 1rem; + text-transform: uppercase; + text-decoration: none; + letter-spacing: 0.025em; + border-radius: 0.25rem; + line-height: 2; +} +.rw-button:hover { + background-color: #718096; + color: #fff; +} +.rw-button.rw-button-small { + font-size: 0.75rem; + border-radius: 0.125rem; + padding: 0.25rem 0.5rem; + line-height: inherit; +} +.rw-button.rw-button-green { + background-color: #48bb78; + color: #fff; +} +.rw-button.rw-button-green:hover { + background-color: #38a169; + color: #fff; +} +.rw-button.rw-button-blue { + background-color: #3182ce; + color: #fff; +} +.rw-button.rw-button-blue:hover { + background-color: #2b6cb0; +} +.rw-button.rw-button-red { + background-color: #e53e3e; + color: #fff; +} +.rw-button.rw-button-red:hover { + background-color: #c53030; +} +.rw-button-icon { + font-size: 1.25rem; + line-height: 1; + margin-right: 0.25rem; +} +.rw-button-group { + display: flex; + justify-content: center; + margin: 0.75rem 0.5rem; +} +.rw-button-group .rw-button { + margin: 0 0.25rem; +} +.rw-form-wrapper .rw-button-group { + margin-top: 2rem; + margin-bottom: 0; +} +.rw-label { + display: block; + margin-top: 1.5rem; + color: #4a5568; + font-weight: 600; +} +.rw-label.rw-label-error { + color: #c53030; +} +.rw-input { + display: block; + margin-top: 0.5rem; + width: 100%; + padding: 0.5rem; + border-width: 1px; + border-color: #e2e8f0; + color: #4a5568; + border-radius: 0.25rem; + outline: none; +} +.rw-input:focus { + border-color: #a0aec0; +} +.rw-input-error { + border-color: #c53030; + color: #c53030; +} +.rw-field-error { + display: block; + margin-top: 0.25rem; + font-weight: 600; + text-transform: uppercase; + font-size: 0.75rem; + color: #c53030; +} +.rw-table-wrapper-responsive { + overflow-x: scroll; +} +.rw-table-wrapper-responsive .rw-table { + min-width: 48rem; +} +.rw-table { + table-layout: auto; + width: 100%; + font-size: 0.875rem; +} .rw-table th, -.rw-table td{padding:.75rem} -.rw-table thead tr{background-color:#e2e8f0;color:#4a5568} -.rw-table th {font-weight:600;text-align:left} -.rw-table thead th {text-align:left} -.rw-table tbody th {text-align:right} -@media (min-width:768px) { - .rw-table tbody th {width:20%} -} -.rw-table tbody tr{background-color:#f7fafc;border-top-width:1px} -.rw-table tbody tr:nth-child(even){background-color:#fff} -.rw-table-actions {display:flex;justify-content:flex-end;align-items: center;height:17px;padding-right:.25rem} -.rw-table-actions .rw-button{background-color:transparent} -.rw-table-actions .rw-button:hover{background-color:#718096;color:#fff} -.rw-table-actions .rw-button-blue{color:#3182ce} -.rw-table-actions .rw-button-blue:hover{background-color:#3182ce;color:#fff} -.rw-table-actions .rw-button-red{color:#e53e3e} -.rw-table-actions .rw-button-red:hover{background-color:#e53e3e;color:#fff} -.rw-text-center{text-align:center} \ No newline at end of file +.rw-table td { + padding: 0.75rem; +} +.rw-table thead tr { + background-color: #e2e8f0; + color: #4a5568; +} +.rw-table th { + font-weight: 600; + text-align: left; +} +.rw-table thead th { + text-align: left; +} +.rw-table tbody th { + text-align: right; +} +@media (min-width: 768px) { + .rw-table tbody th { + width: 20%; + } +} +.rw-table tbody tr { + background-color: #f7fafc; + border-top-width: 1px; +} +.rw-table tbody tr:nth-child(even) { + background-color: #fff; +} +.rw-table-actions { + display: flex; + justify-content: flex-end; + align-items: center; + height: 17px; + padding-right: 0.25rem; +} +.rw-table-actions .rw-button { + background-color: transparent; +} +.rw-table-actions .rw-button:hover { + background-color: #718096; + color: #fff; +} +.rw-table-actions .rw-button-blue { + color: #3182ce; +} +.rw-table-actions .rw-button-blue:hover { + background-color: #3182ce; + color: #fff; +} +.rw-table-actions .rw-button-red { + color: #e53e3e; +} +.rw-table-actions .rw-button-red:hover { + background-color: #e53e3e; + color: #fff; +} +.rw-text-center { + text-align: center; +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js index 86bffe76c18d..e9abd011cac1 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js @@ -12,7 +12,10 @@ export const QUERY = gql` } ` const UPDATE_USER_PROFILE_MUTATION = gql` - mutation UpdateUserProfileMutation($id: Int!, $input: UpdateUserProfileInput!) { + mutation UpdateUserProfileMutation( + $id: Int! + $input: UpdateUserProfileInput! + ) { updateUserProfile(id: $id, input: $input) { id } @@ -22,24 +25,34 @@ const UPDATE_USER_PROFILE_MUTATION = gql` export const Loading = () =>
Loading...
export const Success = ({ userProfile }) => { - const [updateUserProfile, { loading, error }] = useMutation(UPDATE_USER_PROFILE_MUTATION, { - onCompleted: () => { - navigate(routes.userProfiles()) - }, - }) + const [updateUserProfile, { loading, error }] = useMutation( + UPDATE_USER_PROFILE_MUTATION, + { + onCompleted: () => { + navigate(routes.userProfiles()) + }, + } + ) const onSave = (input, id) => { - const castInput = Object.assign(input, { userId: parseInt(input.userId), }) + const castInput = Object.assign(input, { userId: parseInt(input.userId) }) updateUserProfile({ variables: { id, input: castInput } }) } return (
-

Edit UserProfile {userProfile.id}

+

+ Edit UserProfile {userProfile.id} +

- +
) diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js index 4d39015c9d57..69e28b5d4e5a 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js @@ -11,14 +11,17 @@ const CREATE_USER_PROFILE_MUTATION = gql` ` const NewUserProfile = () => { - const [createUserProfile, { loading, error }] = useMutation(CREATE_USER_PROFILE_MUTATION, { - onCompleted: () => { - navigate(routes.userProfiles()) - }, - }) + const [createUserProfile, { loading, error }] = useMutation( + CREATE_USER_PROFILE_MUTATION, + { + onCompleted: () => { + navigate(routes.userProfiles()) + }, + } + ) const onSave = (input) => { - const castInput = Object.assign(input, { userId: parseInt(input.userId), }) + const castInput = Object.assign(input, { userId: parseInt(input.userId) }) createUserProfile({ variables: { input: castInput } }) } diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js index 2257544ff813..5cc0f446d9e0 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js @@ -119,10 +119,7 @@ const PostForm = (props) => {
- + Save
diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js index 60bcbaa84402..82f2bc780dd5 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js @@ -26,10 +26,7 @@ export const Empty = () => { return (
{'No posts yet. '} - + {'Create one?'}
diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js index 564633f3c311..38cc5a444d0a 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js @@ -27,29 +27,37 @@ const Post = ({ post }) => { <>
-

Post {post.id} Detail

+

+ Post {post.id} Detail +

- + + - + + - + + - + + - + + - + + diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js index 142eb142c7c3..9d473b98ce85 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js @@ -5,17 +5,11 @@ const PostsLayout = (props) => {

- + Posts

- +
+
New Post
diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js index a293db893bc9..f031c7089e3f 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js @@ -2,254 +2,547 @@ global.__dirname = __dirname import path from 'path' import { loadGeneratorFixture } from 'src/lib/test' +import { getDefaultArgs } from 'src/lib' import * as scaffold from '../scaffold' -let files - -beforeAll(async () => { - files = await scaffold.files({ model: 'Post' }) -}) - -test('returns exactly 16 files', () => { - expect(Object.keys(files).length).toEqual(16) -}) - -// SDL - -test('creates an sdl', () => { - expect(files).toHaveProperty([ - path.normalize('/path/to/project/api/src/graphql/posts.sdl.js'), - ]) -}) - -// Service - -test('creates a service', () => { - expect(files).toHaveProperty([ - path.normalize('/path/to/project/api/src/services/posts/posts.js'), - ]) -}) - -test('creates a service test', () => { - expect(files).toHaveProperty([ - path.normalize('/path/to/project/api/src/services/posts/posts.test.js'), - ]) -}) - -// styles - -test('creates a stylesheet', () => { - expect( - files[path.normalize('/path/to/project/web/src/scaffold.css')] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) - ) -}) - -// Layout - -test('creates a layout', async () => { - expect( - files[ - path.normalize( - '/path/to/project/web/src/layouts/PostsLayout/PostsLayout.js' - ) - ] - ).toEqual(loadGeneratorFixture('scaffold', path.join('layouts', 'layout.js'))) -}) - -// Pages - -test('creates a edit page', async () => { - expect( - files[ - path.normalize( - '/path/to/project/web/src/pages/EditPostPage/EditPostPage.js' - ) - ] - ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'editPage.js'))) -}) +describe('in javascript (defualt) mode', () => { + let files + + beforeAll(async () => { + files = await scaffold.files({ + ...getDefaultArgs(scaffold.defaults), + model: 'Post', + }) + }) + + test('returns exactly 16 files', () => { + expect(Object.keys(files).length).toEqual(16) + }) + + // SDL + + test('creates an sdl', () => { + expect(files).toHaveProperty([ + path.normalize('/path/to/project/api/src/graphql/posts.sdl.js'), + ]) + }) + + // Service + + test('creates a service', () => { + expect(files).toHaveProperty([ + path.normalize('/path/to/project/api/src/services/posts/posts.js'), + ]) + }) + + test('creates a service test', () => { + expect(files).toHaveProperty([ + path.normalize('/path/to/project/api/src/services/posts/posts.test.js'), + ]) + }) + + // styles + + test('creates a stylesheet', () => { + expect( + files[path.normalize('/path/to/project/web/src/scaffold.css')] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) + ) + }) + + // Layout + + test('creates a layout', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/layouts/PostsLayout/PostsLayout.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('layouts', 'layout.js')) + ) + }) + + // Pages + + test('creates a edit page', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/pages/EditPostPage/EditPostPage.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'editPage.js')) + ) + }) -test('creates a index page', async () => { - expect( - files['/path/to/project/web/src/pages/PostsPage/PostsPage.js'] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('pages', 'indexPage.js')) - ) -}) + test('creates a index page', async () => { + expect( + files['/path/to/project/web/src/pages/PostsPage/PostsPage.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'indexPage.js')) + ) + }) -test('creates a new page', async () => { - expect( - files['/path/to/project/web/src/pages/NewPostPage/NewPostPage.js'] - ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'newPage.js'))) -}) + test('creates a new page', async () => { + expect( + files['/path/to/project/web/src/pages/NewPostPage/NewPostPage.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'newPage.js')) + ) + }) + + test('creates a show page', async () => { + expect( + files[ + path.normalize('/path/to/project/web/src/pages/PostPage/PostPage.js') + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'showPage.js')) + ) + }) + + // Cells + + test('creates an edit cell', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/components/EditPostCell/EditPostCell.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'editCell.js')) + ) + }) -test('creates a show page', async () => { - expect( - files[path.normalize('/path/to/project/web/src/pages/PostPage/PostPage.js')] - ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'showPage.js'))) -}) + test('creates an index cell', async () => { + expect( + files['/path/to/project/web/src/components/PostsCell/PostsCell.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'indexCell.js')) + ) + }) + + test('creates a show cell', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/components/PostCell/PostCell.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'showCell.js')) + ) + }) + + // Components + + test('creates a form component', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/components/PostForm/PostForm.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'form.js')) + ) + }) -// Cells + test('creates an index component', async () => { + expect(files['/path/to/project/web/src/components/Posts/Posts.js']).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'index.js')) + ) + }) + + test('creates a new component', async () => { + expect( + files[ + path.normalize('/path/to/project/web/src/components/NewPost/NewPost.js') + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'new.js')) + ) + }) -test('creates an edit cell', async () => { - expect( - files[ - path.normalize( - '/path/to/project/web/src/components/EditPostCell/EditPostCell.js' + test('creates a show component', async () => { + expect( + files[path.normalize('/path/to/project/web/src/components/Post/Post.js')] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'show.js')) + ) + }) + + // Routes + + test('creates a single-word name routes', async () => { + expect(await scaffold.routes({ model: 'Post' })).toEqual([ + '', + '', + '', + '', + ]) + }) + + test('creates a multi-word name routes', async () => { + expect(await scaffold.routes({ model: 'UserProfile' })).toEqual([ + '', + '', + '', + '', + ]) + }) + + // GraphQL queries + + test('the GraphQL in the index query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + path.normalize( + '/path/to/project/web/src/components/UserProfilesCell/UserProfilesCell.js' + ) + ] + const query = cell.match(/(userProfiles.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) + }) + + test('the GraphQL in the show query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + path.normalize( + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + ) + ] + const query = cell.match(/(userProfile.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) + }) + + test('the GraphQL in the edit query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + path.normalize( + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ) + ] + const query = cell.match(/(userProfile.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) + }) + + // Foreign key casting + + test('creates a new component with int foreign keys converted in onSave', async () => { + const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + + expect( + foreignKeyFiles[ + path.normalize( + '/path/to/project/web/src/components/NewUserProfile/NewUserProfile.js' + ) + ] + ).toEqual( + loadGeneratorFixture( + 'scaffold', + path.join('components', 'foreignKeys', 'new.js') ) - ] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('components', 'editCell.js')) - ) -}) - -test('creates an index cell', async () => { - expect( - files['/path/to/project/web/src/components/PostsCell/PostsCell.js'] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('components', 'indexCell.js')) - ) -}) - -test('creates a show cell', async () => { - expect( - files[ - path.normalize('/path/to/project/web/src/components/PostCell/PostCell.js') - ] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('components', 'showCell.js')) - ) -}) - -// Components - -test('creates a form component', async () => { - expect( - files[ - path.normalize('/path/to/project/web/src/components/PostForm/PostForm.js') - ] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('components', 'form.js')) - ) -}) - -test('creates an index component', async () => { - expect(files['/path/to/project/web/src/components/Posts/Posts.js']).toEqual( - loadGeneratorFixture('scaffold', path.join('components', 'index.js')) - ) -}) - -test('creates a new component', async () => { - expect( - files[ - path.normalize('/path/to/project/web/src/components/NewPost/NewPost.js') - ] - ).toEqual(loadGeneratorFixture('scaffold', path.join('components', 'new.js'))) -}) - -test('creates a show component', async () => { - expect( - files[path.normalize('/path/to/project/web/src/components/Post/Post.js')] - ).toEqual( - loadGeneratorFixture('scaffold', path.join('components', 'show.js')) - ) -}) - -// Routes - -test('creates a single-word name routes', async () => { - expect(await scaffold.routes({ model: 'Post' })).toEqual([ - '', - '', - '', - '', - ]) -}) - -test('creates a multi-word name routes', async () => { - expect(await scaffold.routes({ model: 'UserProfile' })).toEqual([ - '', - '', - '', - '', - ]) -}) - -// GraphQL queries - -test('the GraphQL in the index query does not contain object types', async () => { - const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) - const cell = - userProfileFiles[ - path.normalize( - '/path/to/project/web/src/components/UserProfilesCell/UserProfilesCell.js' + ) + }) + + test('creates an edit component with int foreign keys converted in onSave', async () => { + const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + + expect( + foreignKeyFiles[ + path.normalize( + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ) + ] + ).toEqual( + loadGeneratorFixture( + 'scaffold', + path.join('components', 'foreignKeys', 'edit.js') ) - ] - const query = cell.match(/(userProfiles.*?\})/s)[1] - - expect(query).not.toMatch(/^\s+user$/m) + ) + }) }) -test('the GraphQL in the show query does not contain object types', async () => { - const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) - const cell = - userProfileFiles[ - path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' - ) - ] - const query = cell.match(/(userProfile.*?\})/s)[1] - - expect(query).not.toMatch(/^\s+user$/m) -}) +describe('in typescript mode', () => { + let files + + beforeAll(async () => { + files = await scaffold.files({ + ...getDefaultArgs(scaffold.defaults), + model: 'Post', + typescript: true, + }) + }) + + test('returns exactly 16 files', () => { + expect(Object.keys(files).length).toEqual(16) + }) + + // SDL + + test('creates an sdl', () => { + expect(files).toHaveProperty([ + path.normalize('/path/to/project/api/src/graphql/posts.sdl.ts'), + ]) + }) + + // Service + + test('creates a service', () => { + expect(files).toHaveProperty([ + path.normalize('/path/to/project/api/src/services/posts/posts.ts'), + ]) + }) + + test('creates a service test', () => { + expect(files).toHaveProperty([ + path.normalize('/path/to/project/api/src/services/posts/posts.test.ts'), + ]) + }) + + // styles + + test('creates a stylesheet', () => { + expect( + files[path.normalize('/path/to/project/web/src/scaffold.css')] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) + ) + }) + + // Layout + + test('creates a layout', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/layouts/PostsLayout/PostsLayout.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('layouts', 'layout.js')) + ) + }) + + // Pages + + test('creates a edit page', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/pages/EditPostPage/EditPostPage.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'editPage.js')) + ) + }) -test('the GraphQL in the edit query does not contain object types', async () => { - const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) - const cell = - userProfileFiles[ - path.normalize( - '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' - ) - ] - const query = cell.match(/(userProfile.*?\})/s)[1] + test('creates a index page', async () => { + expect( + files['/path/to/project/web/src/pages/PostsPage/PostsPage.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'indexPage.js')) + ) + }) - expect(query).not.toMatch(/^\s+user$/m) -}) + test('creates a new page', async () => { + expect( + files['/path/to/project/web/src/pages/NewPostPage/NewPostPage.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'newPage.js')) + ) + }) + + test('creates a show page', async () => { + expect( + files[ + path.normalize('/path/to/project/web/src/pages/PostPage/PostPage.js') + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'showPage.js')) + ) + }) + + // Cells + + test('creates an edit cell', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/components/EditPostCell/EditPostCell.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'editCell.js')) + ) + }) -// Foreign key casting + test('creates an index cell', async () => { + expect( + files['/path/to/project/web/src/components/PostsCell/PostsCell.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'indexCell.js')) + ) + }) + + test('creates a show cell', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/components/PostCell/PostCell.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'showCell.js')) + ) + }) + + // Components + + test('creates a form component', async () => { + expect( + files[ + path.normalize( + '/path/to/project/web/src/components/PostForm/PostForm.js' + ) + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'form.js')) + ) + }) -test('creates a new component with int foreign keys converted in onSave', async () => { - const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + test('creates an index component', async () => { + expect(files['/path/to/project/web/src/components/Posts/Posts.js']).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'index.js')) + ) + }) + + test('creates a new component', async () => { + expect( + files[ + path.normalize('/path/to/project/web/src/components/NewPost/NewPost.js') + ] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'new.js')) + ) + }) - expect( - foreignKeyFiles[ - path.normalize( - '/path/to/project/web/src/components/NewUserProfile/NewUserProfile.js' + test('creates a show component', async () => { + expect( + files[path.normalize('/path/to/project/web/src/components/Post/Post.js')] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'show.js')) + ) + }) + + // Routes + + test('creates a single-word name routes', async () => { + expect(await scaffold.routes({ model: 'Post' })).toEqual([ + '', + '', + '', + '', + ]) + }) + + test('creates a multi-word name routes', async () => { + expect(await scaffold.routes({ model: 'UserProfile' })).toEqual([ + '', + '', + '', + '', + ]) + }) + + // GraphQL queries + + test('the GraphQL in the index query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + path.normalize( + '/path/to/project/web/src/components/UserProfilesCell/UserProfilesCell.js' + ) + ] + const query = cell.match(/(userProfiles.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) + }) + + test('the GraphQL in the show query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + path.normalize( + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + ) + ] + const query = cell.match(/(userProfile.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) + }) + + test('the GraphQL in the edit query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + path.normalize( + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ) + ] + const query = cell.match(/(userProfile.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) + }) + + // Foreign key casting + + test('creates a new component with int foreign keys converted in onSave', async () => { + const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + + expect( + foreignKeyFiles[ + path.normalize( + '/path/to/project/web/src/components/NewUserProfile/NewUserProfile.js' + ) + ] + ).toEqual( + loadGeneratorFixture( + 'scaffold', + path.join('components', 'foreignKeys', 'new.js') ) - ] - ).toEqual( - loadGeneratorFixture( - 'scaffold', - path.join('components', 'foreignKeys', 'new.js') ) - ) -}) - -test('creates an edit component with int foreign keys converted in onSave', async () => { - const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) - - expect( - foreignKeyFiles[ - path.normalize( - '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + }) + + test('creates an edit component with int foreign keys converted in onSave', async () => { + const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + + expect( + foreignKeyFiles[ + path.normalize( + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ) + ] + ).toEqual( + loadGeneratorFixture( + 'scaffold', + path.join('components', 'foreignKeys', 'edit.js') ) - ] - ).toEqual( - loadGeneratorFixture( - 'scaffold', - path.join('components', 'foreignKeys', 'edit.js') ) - ) + }) }) diff --git a/packages/cli/src/commands/generate/scaffold/scaffold.js b/packages/cli/src/commands/generate/scaffold/scaffold.js index 2cff717496f6..936132a2b210 100644 --- a/packages/cli/src/commands/generate/scaffold/scaffold.js +++ b/packages/cli/src/commands/generate/scaffold/scaffold.js @@ -15,6 +15,7 @@ import { writeFile, asyncForEach, getSchema, + getDefaultArgs, getPaths, writeFilesTask, addRoutesToRouterTask, @@ -22,8 +23,11 @@ import { import c from 'src/lib/colors' import { relationsForModel, intForeignKeysForModel } from '../helpers' -import { files as sdlFiles } from '../sdl/sdl' -import { files as serviceFiles } from '../service/service' +import { files as sdlFiles, builder as sdlBuilder } from '../sdl/sdl' +import { + files as serviceFiles, + builder as serviceBuilder, +} from '../service/service' const NON_EDITABLE_COLUMNS = ['id', 'createdAt', 'updatedAt'] const ASSETS = fs.readdirSync( @@ -46,15 +50,29 @@ const getIdType = (model) => { return model.fields.find((field) => field.isId)?.type } -export const files = async ({ model: name, path: scaffoldPath = '' }) => { +export const files = async ({ + model: name, + path: scaffoldPath = '', + typescript, + javascript, +}) => { const model = await getSchema(pascalcase(pluralize.singular(name))) return { - ...(await sdlFiles({ name, crud: true })), + ...(await sdlFiles({ + ...getDefaultArgs(sdlBuilder), + name, + crud: true, + typescript, + javascript, + })), ...(await serviceFiles({ + ...getDefaultArgs(serviceBuilder), name, crud: true, relations: relationsForModel(model), + typescript, + javascript, })), ...assetFiles(name), ...layoutFiles(name, scaffoldPath), @@ -313,6 +331,22 @@ const addScaffoldImport = () => { return 'Added scaffold import to index.js' } +export const defaults = { + force: { + default: false, + type: 'boolean', + }, + typescript: { + type: 'boolean', + default: false, + desc: 'Generate TypeScript files', + }, + javascript: { + type: 'boolean', + default: true, + desc: 'Generate JavaScript files', + }, +} export const command = 'scaffold ' export const desc = 'Generate Pages, SDL, and Services files based on a given DB schema Model. Also accepts .' @@ -321,18 +355,17 @@ export const builder = (yargs) => { description: "Model to scaffold. You can also use to nest files by type at the given path directory (or directories). For example, 'rw g scaffold admin/post'.", }) - yargs.option('force', { - default: false, - type: 'boolean', + Object.entries(defaults).forEach(([option, config]) => { + yargs.option(option, config) }) } -const tasks = ({ model, path, force }) => { +const tasks = ({ model, path, force, typescript, javascript }) => { return new Listr( [ { title: 'Generating scaffold files...', task: async () => { - const f = await files({ model, path }) + const f = await files({ model, path, typescript, javascript }) return writeFilesTask(f, { overwriteExisting: force }) }, }, @@ -351,10 +384,15 @@ const tasks = ({ model, path, force }) => { ) } -export const handler = async ({ model: modelArg, force }) => { +export const handler = async ({ + model: modelArg, + force, + typescript, + javascript, +}) => { const { model, path } = splitPathAndModel(modelArg) - const t = tasks({ model, path, force }) + const t = tasks({ model, path, force, typescript, javascript }) try { await t.run() } catch (e) { diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.js index c43ed4054d3b..78bc7791feab 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.js @@ -1,3 +1,4 @@ +import gql from 'graphql-tag' export const schema = gql` type Shoe { id: Int! diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.ts b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.ts new file mode 100644 index 000000000000..22cf0aa7d6ee --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/enumGeneratedSdl.ts @@ -0,0 +1,33 @@ +import gql from 'graphql-tag' + +export const schema = gql` + type Shoe { + id: Int! + color: Color! + } + + enum Color { + RED + GREEN + BLUE + } + + type Query { + shoes: [Shoe!]! + shoe(id: Int!): Shoe! + } + + input CreateShoeInput { + color: Color! + } + + input UpdateShoeInput { + color: Color + } + + type Mutation { + createShoe(input: CreateShoeInput!): Shoe! + updateShoe(id: Int!, input: UpdateShoeInput!): Shoe! + deleteShoe(id: Int!): Shoe! + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js index 7be7ce0aa6ae..44a5ea1a156c 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js @@ -1,3 +1,4 @@ +import gql from 'graphql-tag' export const schema = gql` type UserProfile { id: Int! diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.ts b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.ts new file mode 100644 index 000000000000..bd895becd864 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.ts @@ -0,0 +1,24 @@ +import gql from 'graphql-tag' + +export const schema = gql` + type UserProfile { + id: Int! + username: String! + userId: Int! + user: User! + } + + type Query { + userProfiles: [UserProfile!]! + } + + input CreateUserProfileInput { + username: String! + userId: Int! + } + + input UpdateUserProfileInput { + username: String + userId: Int + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js index 4daa293cfb90..cf235c22a1b9 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js @@ -1,3 +1,4 @@ +import gql from 'graphql-tag' export const schema = gql` type UserProfile { id: Int! diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.ts b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.ts new file mode 100644 index 000000000000..978dd3902eb1 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.ts @@ -0,0 +1,31 @@ +import gql from 'graphql-tag' + +export const schema = gql` + type UserProfile { + id: Int! + username: String! + userId: Int! + user: User! + } + + type Query { + userProfiles: [UserProfile!]! + userProfile(id: Int!): UserProfile! + } + + input CreateUserProfileInput { + username: String! + userId: Int! + } + + input UpdateUserProfileInput { + username: String + userId: Int + } + + type Mutation { + createUserProfile(input: CreateUserProfileInput!): UserProfile! + updateUserProfile(id: Int!, input: UpdateUserProfileInput!): UserProfile! + deleteUserProfile(id: Int!): UserProfile! + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js index 48842d8062ba..dfb8f93f331a 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js @@ -1,3 +1,4 @@ +import gql from 'graphql-tag' export const schema = gql` type User { id: Int! diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.ts b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.ts new file mode 100644 index 000000000000..70df6d89f9fe --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.ts @@ -0,0 +1,27 @@ +import gql from 'graphql-tag' + +export const schema = gql` + type User { + id: Int! + name: String + email: String! + isAdmin: Boolean! + profiles: UserProfile + } + + type Query { + users: [User!]! + } + + input CreateUserInput { + name: String + email: String! + isAdmin: Boolean! + } + + input UpdateUserInput { + name: String + email: String + isAdmin: Boolean + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js index 22c00836643d..623ed9d92641 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js @@ -1,3 +1,4 @@ +import gql from 'graphql-tag' export const schema = gql` type Post { id: Int! diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.ts b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.ts new file mode 100644 index 000000000000..03e2e78cb282 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.ts @@ -0,0 +1,42 @@ +import gql from 'graphql-tag' + +export const schema = gql` + type Post { + id: Int! + title: String! + slug: String! + author: String! + body: String! + image: String + postedAt: DateTime + } + + type Query { + posts: [Post!]! + post(id: Int!): Post! + } + + input CreatePostInput { + title: String! + slug: String! + author: String! + body: String! + image: String + postedAt: DateTime + } + + input UpdatePostInput { + title: String + slug: String + author: String + body: String + image: String + postedAt: DateTime + } + + type Mutation { + createPost(input: CreatePostInput!): Post! + updatePost(id: Int!, input: UpdatePostInput!): Post! + deletePost(id: Int!): Post! + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js index 783518f4c501..622e15ee183e 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js @@ -1,6 +1,7 @@ global.__dirname = __dirname import { loadGeneratorFixture } from 'src/lib/test' +import { getDefaultArgs } from 'src/lib' import * as sdl from '../sdl' @@ -8,62 +9,116 @@ afterEach(() => { jest.clearAllMocks() }) -test('returns exactly 3 files', async () => { - const files = await sdl.files({ name: 'Post', crud: false }) +const extensionForBaseArgs = (baseArgs) => + baseArgs && baseArgs.typescript ? 'ts' : 'js' - expect(Object.keys(files).length).toEqual(3) -}) +const itReturnsExactlyThreeFiles = (baseArgs = {}) => { + test('returns exactly 3 files', async () => { + const files = await sdl.files({ ...baseArgs, name: 'Post', crud: false }) + + expect(Object.keys(files).length).toEqual(3) + }) +} // in this case we'll trust that a service and test are actually created // with the correct filename, but the contents of that file should be the // job of the service tests -test('creates a service', async () => { - const files = await sdl.files({ name: 'User', crud: false }) - - expect(files).toHaveProperty([ - '/path/to/project/api/src/services/users/users.js', - ]) - expect(files).toHaveProperty([ - '/path/to/project/api/src/services/users/users.test.js', - ]) -}) - -test('creates a single word sdl file', async () => { - const files = await sdl.files({ name: 'User', crud: false }) - - expect(files['/path/to/project/api/src/graphql/users.sdl.js']).toEqual( - loadGeneratorFixture('sdl', 'singleWordSdl.js') - ) -}) - -test('creates a multi word sdl file', async () => { - const files = await sdl.files({ name: 'UserProfile', crud: false }) - - expect(files['/path/to/project/api/src/graphql/userProfiles.sdl.js']).toEqual( - loadGeneratorFixture('sdl', 'multiWordSdl.js') - ) -}) - -test('creates a single word sdl file with CRUD actions', async () => { - const files = await sdl.files({ name: 'Post', crud: true }) - - expect(files['/path/to/project/api/src/graphql/posts.sdl.js']).toEqual( - loadGeneratorFixture('sdl', 'singleWordSdlCrud.js') - ) -}) - -test('creates a multi word sdl file with CRUD actions', async () => { - const files = await sdl.files({ name: 'UserProfile', crud: true }) - - expect(files['/path/to/project/api/src/graphql/userProfiles.sdl.js']).toEqual( - loadGeneratorFixture('sdl', 'multiWordSdlCrud.js') - ) +const itCreatesAService = (baseArgs = {}) => { + test('creates a service', async () => { + const files = await sdl.files({ ...baseArgs, name: 'User', crud: false }) + const extension = extensionForBaseArgs(baseArgs) + + expect(files).toHaveProperty([ + `/path/to/project/api/src/services/users/users.${extension}`, + ]) + expect(files).toHaveProperty([ + `/path/to/project/api/src/services/users/users.test.${extension}`, + ]) + }) +} + +const itCreatesASingleWordSDLFile = (baseArgs = {}) => { + test('creates a single word sdl file', async () => { + const files = await sdl.files({ ...baseArgs, name: 'User' }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/graphql/users.sdl.${extension}`] + ).toEqual(loadGeneratorFixture('sdl', `singleWordSdl.${extension}`)) + }) +} + +const itCreatesAMultiWordSDLFile = (baseArgs = {}) => { + test('creates a multi word sdl file', async () => { + const files = await sdl.files({ + ...baseArgs, + name: 'UserProfile', + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/graphql/userProfiles.sdl.${extension}`] + ).toEqual(loadGeneratorFixture('sdl', `multiWordSdl.${extension}`)) + }) +} + +const itCreatesASingleWordSDLFileWithCRUD = (baseArgs = {}) => { + test('creates a single word sdl file with CRUD actions', async () => { + const files = await sdl.files({ ...baseArgs, name: 'Post', crud: true }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/graphql/posts.sdl.${extension}`] + ).toEqual(loadGeneratorFixture('sdl', `singleWordSdlCrud.${extension}`)) + }) +} + +const itCreateAMultiWordSDLFileWithCRUD = (baseArgs = {}) => { + test('creates a multi word sdl file with CRUD actions', async () => { + const files = await sdl.files({ + ...baseArgs, + name: 'UserProfile', + crud: true, + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/graphql/userProfiles.sdl.${extension}`] + ).toEqual(loadGeneratorFixture('sdl', `multiWordSdlCrud.${extension}`)) + }) +} + +const itCreatesASDLFileWithEnumDefinitions = (baseArgs = {}) => { + test('creates a sdl file with enum definitions', async () => { + const files = await sdl.files({ ...baseArgs, name: 'Shoe', crud: true }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/graphql/shoes.sdl.${extension}`] + ).toEqual(loadGeneratorFixture('sdl', `enumGeneratedSdl.${extension}`)) + }) +} + +describe('in javascript mode', () => { + const baseArgs = getDefaultArgs(sdl.builder) + + itReturnsExactlyThreeFiles(baseArgs) + itCreatesAService(baseArgs) + itCreatesASingleWordSDLFile(baseArgs) + itCreatesAMultiWordSDLFile(baseArgs) + itCreatesASingleWordSDLFileWithCRUD(baseArgs) + itCreateAMultiWordSDLFileWithCRUD(baseArgs) + itCreatesASDLFileWithEnumDefinitions(baseArgs) }) -test('creates a sdl file with enum definitions', async () => { - const files = await sdl.files({ name: 'Shoe', crud: true }) +describe('in typescript mode', () => { + const baseArgs = { ...getDefaultArgs(sdl.builder), typescript: true } - expect(files['/path/to/project/api/src/graphql/shoes.sdl.js']).toEqual( - loadGeneratorFixture('sdl', 'enumGeneratedSdl.js') - ) + itReturnsExactlyThreeFiles(baseArgs) + itCreatesAService(baseArgs) + itCreatesASingleWordSDLFile(baseArgs) + itCreatesAMultiWordSDLFile(baseArgs) + itCreatesASingleWordSDLFileWithCRUD(baseArgs) + itCreateAMultiWordSDLFileWithCRUD(baseArgs) + itCreatesASDLFileWithEnumDefinitions(baseArgs) }) diff --git a/packages/cli/src/commands/generate/sdl/sdl.js b/packages/cli/src/commands/generate/sdl/sdl.js index a47ab538dfea..43553087ee57 100644 --- a/packages/cli/src/commands/generate/sdl/sdl.js +++ b/packages/cli/src/commands/generate/sdl/sdl.js @@ -7,6 +7,7 @@ import pluralize from 'pluralize' import { generateTemplate, + transformTSToJS, getSchema, getPaths, writeFilesTask, @@ -105,7 +106,7 @@ const sdlFromSchemaModel = async (name) => { } } -export const files = async ({ name, crud }) => { +export const files = async ({ name, crud, typescript, javascript }) => { const { query, createInput, @@ -115,8 +116,8 @@ export const files = async ({ name, crud }) => { enums, } = await sdlFromSchemaModel(pascalcase(pluralize.singular(name))) - const template = generateTemplate( - path.join('sdl', 'templates', 'sdl.js.template'), + let template = generateTemplate( + path.join('sdl', 'templates', `sdl.ts.template`), { name, crud, @@ -128,13 +129,19 @@ export const files = async ({ name, crud }) => { } ) - const outputPath = path.join( + const extension = typescript === true ? 'ts' : 'js' + let outputPath = path.join( getPaths().api.graphql, - `${camelcase(pluralize(name))}.sdl.js` + `${camelcase(pluralize(name))}.sdl.${extension}` ) + + if (javascript && !typescript) { + template = transformTSToJS(outputPath, template) + } + return { [outputPath]: template, - ...(await serviceFiles({ name, crud, relations })), + ...(await serviceFiles({ name, crud, relations, typescript, javascript })), } } @@ -144,15 +151,23 @@ export const builder = { services: { type: 'boolean', default: true }, crud: { type: 'boolean', default: false }, force: { type: 'boolean', default: false }, + javascript: { type: 'boolean', default: true }, + typescript: { type: 'boolean', default: false }, } // TODO: Add --dry-run command -export const handler = async ({ model, crud, force }) => { +export const handler = async ({ + model, + crud, + force, + typescript, + javascript, +}) => { const tasks = new Listr( [ { title: 'Generating SDL files...', task: async () => { - const f = await files({ name: model, crud }) + const f = await files({ name: model, crud, typescript, javascript }) return writeFilesTask(f, { overwriteExisting: force }) }, }, diff --git a/packages/cli/src/commands/generate/sdl/templates/sdl.ts.template b/packages/cli/src/commands/generate/sdl/templates/sdl.ts.template new file mode 100644 index 000000000000..d06d47b031a2 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/templates/sdl.ts.template @@ -0,0 +1,30 @@ +import gql from 'graphql-tag' + +export const schema = gql` + type ${singularPascalName} { + ${query} + } +<% if (enums.length > 0) {%> +<% enums.forEach((enumDef, idx)=> {%> enum ${enums[idx].name} {<% enums[idx].values.forEach((enumDefValue, idk)=> { %> + ${enums[idx].values[idk].name}<% }) %> + } +<%}) %><% } %> + type Query { + ${pluralCamelName}: [${singularPascalName}!]!<% if (crud) { %> + ${singularCamelName}(id: ${idType}!): ${singularPascalName}!<% } %> + } + + input Create${singularPascalName}Input { + ${createInput} + } + + input Update${singularPascalName}Input { + ${updateInput} + }<% if (crud) { %> + + type Mutation { + create${singularPascalName}(input: Create${singularPascalName}Input!): ${singularPascalName}! + update${singularPascalName}(id: ${idType}!, input: Update${singularPascalName}Input!): ${singularPascalName}! + delete${singularPascalName}(id: ${idType}!): ${singularPascalName}! + }<% } %> +` diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js index 2f54fb24b2c6..5a456b92a707 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js @@ -1,5 +1,4 @@ import { db } from 'src/lib/db' - export const userProfiles = () => { return db.userProfile.findMany() } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js index 0c1f26ab30ed..ae85ba4ab5b7 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js @@ -1,5 +1,6 @@ +/* import { userProfiles } from './userProfiles' - +*/ describe('userProfiles', () => { it('returns true', () => { expect(true).toBe(true) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.ts similarity index 96% rename from packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js rename to packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.ts index 0c1f26ab30ed..7002f191355f 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.ts @@ -1,4 +1,6 @@ +/* import { userProfiles } from './userProfiles' +*/ describe('userProfiles', () => { it('returns true', () => { diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.ts similarity index 100% rename from packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.js rename to packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.ts diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js deleted file mode 100644 index 8be74a61b2e7..000000000000 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js +++ /dev/null @@ -1,30 +0,0 @@ -import { db } from 'src/lib/db' - -export const userProfiles = () => { - return db.userProfile.findMany() -} - -export const userProfile = ({ id }) => { - return db.userProfile.findOne({ - where: { id }, - }) -} - -export const createUserProfile = ({ input }) => { - return db.userProfile.create({ - data: input, - }) -} - -export const updateUserProfile = ({ id, input }) => { - return db.userProfile.update({ - data: input, - where: { id }, - }) -} - -export const deleteUserProfile = ({ id }) => { - return db.userProfile.delete({ - where: { id }, - }) -} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js index 8be74a61b2e7..7550866414f5 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js @@ -1,30 +1,31 @@ import { db } from 'src/lib/db' - export const userProfiles = () => { return db.userProfile.findMany() } - export const userProfile = ({ id }) => { return db.userProfile.findOne({ - where: { id }, + where: { + id, + }, }) } - export const createUserProfile = ({ input }) => { return db.userProfile.create({ data: input, }) } - export const updateUserProfile = ({ id, input }) => { return db.userProfile.update({ data: input, - where: { id }, + where: { + id, + }, }) } - export const deleteUserProfile = ({ id }) => { return db.userProfile.delete({ - where: { id }, + where: { + id, + }, }) } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js index 0c1f26ab30ed..ae85ba4ab5b7 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js @@ -1,5 +1,6 @@ +/* import { userProfiles } from './userProfiles' - +*/ describe('userProfiles', () => { it('returns true', () => { expect(true).toBe(true) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.ts similarity index 96% rename from packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js rename to packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.ts index 0c1f26ab30ed..7002f191355f 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.ts @@ -1,4 +1,6 @@ +/* import { userProfiles } from './userProfiles' +*/ describe('userProfiles', () => { it('returns true', () => { diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.ts b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.ts new file mode 100644 index 000000000000..a2bf9912d8f8 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.ts @@ -0,0 +1,44 @@ +import { + UserProfileWhereUniqueInput, + UserProfileCreateInput, + UserProfileUpdateInput, +} from '@prisma/client' +import { db } from 'src/lib/db' + +export const userProfiles = () => { + return db.userProfile.findMany() +} + +export const userProfile = ({ id }: UserProfileWhereUniqueInput) => { + return db.userProfile.findOne({ + where: { id }, + }) +} + +interface CreateUserProfileArgs { + input: UserProfileCreateInput +} + +export const createUserProfile = ({ input }: CreateUserProfileArgs) => { + return db.userProfile.create({ + data: input, + }) +} + +interface UpdateUserProfileArgs { + where: UserProfileWhereUniqueInput + input: UserProfileUpdateInput +} + +export const updateUserProfile = ({ id, input }: UpdateUserProfileArgs) => { + return db.userProfile.update({ + data: input, + where: { id }, + }) +} + +export const deleteUserProfile = ({ id }: UserProfileWhereUniqueInput) => { + return db.userProfile.delete({ + where: { id }, + }) +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js index b5bfed92100b..99fe7eeb6c5f 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js @@ -1,5 +1,4 @@ import { db } from 'src/lib/db' - export const users = () => { return db.user.findMany() } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js index 5a73412f0b9d..f31a4e004ff4 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js @@ -1,5 +1,6 @@ +/* import { users } from './users' - +*/ describe('users', () => { it('returns true', () => { expect(true).toBe(true) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.ts similarity index 95% rename from packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js rename to packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.ts index 5a73412f0b9d..bac7cb4ed5c6 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.ts @@ -1,4 +1,6 @@ +/* import { users } from './users' +*/ describe('users', () => { it('returns true', () => { diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.ts similarity index 100% rename from packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.js rename to packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.ts diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js deleted file mode 100644 index be53d32bf06f..000000000000 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js +++ /dev/null @@ -1,30 +0,0 @@ -import { db } from 'src/lib/db' - -export const posts = () => { - return db.post.findMany() -} - -export const post = ({ id }) => { - return db.post.findOne({ - where: { id }, - }) -} - -export const createPost = ({ input }) => { - return db.post.create({ - data: input, - }) -} - -export const updatePost = ({ id, input }) => { - return db.post.update({ - data: input, - where: { id }, - }) -} - -export const deletePost = ({ id }) => { - return db.post.delete({ - where: { id }, - }) -} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js index b1c72da817c6..7ee9591f01a5 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js @@ -1,9 +1,14 @@ import { db } from 'src/lib/db' - export const users = () => { return db.user.findMany() } - export const User = { - identity: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).identity(), + identity: (_obj, { root }) => + db.user + .findOne({ + where: { + id: root.id, + }, + }) + .identity(), } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.ts b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.ts new file mode 100644 index 000000000000..6dcc0c2b3f24 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.ts @@ -0,0 +1,12 @@ +import { UserWhereUniqueInput } from '@prisma/client' +import { ResolverArgs } from '@redwoodjs/api/dist/types' +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} + +export const User = { + identity: (_obj, { root }: ResolverArgs) => + db.user.findOne({ where: { id: root.id } }).identity(), +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js index be53d32bf06f..586db6356700 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js @@ -1,30 +1,31 @@ import { db } from 'src/lib/db' - export const posts = () => { return db.post.findMany() } - export const post = ({ id }) => { return db.post.findOne({ - where: { id }, + where: { + id, + }, }) } - export const createPost = ({ input }) => { return db.post.create({ data: input, }) } - export const updatePost = ({ id, input }) => { return db.post.update({ data: input, - where: { id }, + where: { + id, + }, }) } - export const deletePost = ({ id }) => { return db.post.delete({ - where: { id }, + where: { + id, + }, }) } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js index b5ff8afbdfc3..ecc2cd3a8f68 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js @@ -1,5 +1,6 @@ +/* import { posts } from './posts' - +*/ describe('posts', () => { it('returns true', () => { expect(true).toBe(true) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.ts similarity index 95% rename from packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js rename to packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.ts index b5ff8afbdfc3..f23483ac34b4 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.ts @@ -1,4 +1,6 @@ +/* import { posts } from './posts' +*/ describe('posts', () => { it('returns true', () => { diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.ts b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.ts new file mode 100644 index 000000000000..2b47200240d1 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.ts @@ -0,0 +1,44 @@ +import { + PostWhereUniqueInput, + PostCreateInput, + PostUpdateInput, +} from '@prisma/client' +import { db } from 'src/lib/db' + +export const posts = () => { + return db.post.findMany() +} + +export const post = ({ id }: PostWhereUniqueInput) => { + return db.post.findOne({ + where: { id }, + }) +} + +interface CreatePostArgs { + input: PostCreateInput +} + +export const createPost = ({ input }: CreatePostArgs) => { + return db.post.create({ + data: input, + }) +} + +interface UpdatePostArgs { + where: PostWhereUniqueInput + input: PostUpdateInput +} + +export const updatePost = ({ id, input }: UpdatePostArgs) => { + return db.post.update({ + data: input, + where: { id }, + }) +} + +export const deletePost = ({ id }: PostWhereUniqueInput) => { + return db.post.delete({ + where: { id }, + }) +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js index 200d1809af15..0071f84f37f1 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js @@ -1,9 +1,14 @@ import { db } from 'src/lib/db' - export const users = () => { return db.user.findMany() } - export const User = { - userProfiles: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).userProfiles(), + userProfiles: (_obj, { root }) => + db.user + .findOne({ + where: { + id: root.id, + }, + }) + .userProfiles(), } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.ts b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.ts new file mode 100644 index 000000000000..ace7796aeda6 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.ts @@ -0,0 +1,12 @@ +import { UserWhereUniqueInput } from '@prisma/client' +import { ResolverArgs } from '@redwoodjs/api/dist/types' +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} + +export const User = { + userProfiles: (_obj, { root }: ResolverArgs) => + db.user.findOne({ where: { id: root.id } }).userProfiles(), +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js index 1dfea708f072..aaeb25f758ca 100644 --- a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js @@ -1,10 +1,22 @@ import { db } from 'src/lib/db' - export const users = () => { return db.user.findMany() } - export const User = { - userProfiles: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).userProfiles(), - identity: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).identity(), + userProfiles: (_obj, { root }) => + db.user + .findOne({ + where: { + id: root.id, + }, + }) + .userProfiles(), + identity: (_obj, { root }) => + db.user + .findOne({ + where: { + id: root.id, + }, + }) + .identity(), } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.ts b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.ts new file mode 100644 index 000000000000..b1b76038928b --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.ts @@ -0,0 +1,14 @@ +import { UserWhereUniqueInput } from '@prisma/client' +import { ResolverArgs } from '@redwoodjs/api/dist/types' +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} + +export const User = { + userProfiles: (_obj, { root }: ResolverArgs) => + db.user.findOne({ where: { id: root.id } }).userProfiles(), + identity: (_obj, { root }: ResolverArgs) => + db.user.findOne({ where: { id: root.id } }).identity(), +} diff --git a/packages/cli/src/commands/generate/service/__tests__/service.test.js b/packages/cli/src/commands/generate/service/__tests__/service.test.js index 3ae77b8b6a07..5dbe213840f3 100644 --- a/packages/cli/src/commands/generate/service/__tests__/service.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/service.test.js @@ -1,146 +1,230 @@ global.__dirname = __dirname + import { loadGeneratorFixture } from 'src/lib/test' +import { getDefaultArgs } from 'src/lib' import * as service from '../service' -test('returns exactly 2 files', async () => { - const files = await service.files({ - name: 'User', - crud: false, - relations: [], - }) +const extensionForBaseArgs = (baseArgs) => + baseArgs && baseArgs.typescript ? 'ts' : 'js' - expect(Object.keys(files).length).toEqual(2) -}) +const itReturnsExactly2Files = (baseArgs) => { + test('returns exactly 2 files', async () => { + const files = await service.files({ + ...baseArgs, + name: 'User', + }) -test('creates a single word service file', async () => { - const files = await service.files({ - name: 'User', - crud: false, - relations: [], + expect(Object.keys(files).length).toEqual(2) }) - - expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( - loadGeneratorFixture('service', 'singleWord.js') - ) -}) - -test('creates a single word service test file', async () => { - const files = await service.files({ - name: 'User', - crud: false, - relations: null, +} +const itCreatesASingleWordServiceFile = (baseArgs) => { + test('creates a single word service file', async () => { + const files = await service.files({ + ...baseArgs, + name: 'User', + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/users/users.${extension}`] + ).toEqual(loadGeneratorFixture('service', `singleWord.${extension}`)) }) - - expect( - files['/path/to/project/api/src/services/users/users.test.js'] - ).toEqual(loadGeneratorFixture('service', 'singleWord.test.js')) -}) - -test('creates a multi word service file', async () => { - const files = await service.files({ - name: 'UserProfile', - crud: false, - relations: null, +} +const itCreatesASingleWordServiceTestFile = (baseArgs) => { + test('creates a single word service test file', async () => { + const files = await service.files({ + ...baseArgs, + name: 'User', + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/users/users.test.${extension}`] + ).toEqual(loadGeneratorFixture('service', `singleWord.test.${extension}`)) }) - - expect( - files['/path/to/project/api/src/services/userProfiles/userProfiles.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWord.js')) -}) - -test('creates a multi word service test file', async () => { - const files = await service.files({ - name: 'UserProfile', - crud: false, - relations: null, +} + +const itCreatesAMultiWordServiceFile = (baseArgs) => { + test('creates a multi word service file', async () => { + const files = await service.files({ + ...baseArgs, + name: 'UserProfile', + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[ + `/path/to/project/api/src/services/userProfiles/userProfiles.${extension}` + ] + ).toEqual(loadGeneratorFixture('service', `multiWord.${extension}`)) }) - - expect( - files['/path/to/project/api/src/services/userProfiles/userProfiles.test.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWord.test.js')) -}) - -test('creates a single word service file with CRUD actions', async () => { - const files = await service.files({ - name: 'Post', - crud: true, - relations: null, +} + +const itCreatesAMultiWordServiceTestFile = (baseArgs) => { + test('creates a multi word service test file', async () => { + const files = await service.files({ + ...baseArgs, + name: 'UserProfile', + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[ + `/path/to/project/api/src/services/userProfiles/userProfiles.test.${extension}` + ] + ).toEqual(loadGeneratorFixture('service', `multiWord.test.${extension}`)) }) - - expect(files['/path/to/project/api/src/services/posts/posts.js']).toEqual( - loadGeneratorFixture('service', 'singleWord_crud.js') - ) -}) - -test('creates a service test file with CRUD actions', async () => { - const files = await service.files({ - name: 'Post', - crud: true, - relations: null, +} + +const itCreatesASingleWordServiceFileWithCRUDActions = (baseArgs) => { + test('creates a single word service file with CRUD actions', async () => { + const files = await service.files({ + ...baseArgs, + name: 'Post', + crud: true, + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/posts/posts.${extension}`] + ).toEqual(loadGeneratorFixture('service', `singleWord_crud.${extension}`)) }) - - expect( - files['/path/to/project/api/src/services/posts/posts.test.js'] - ).toEqual(loadGeneratorFixture('service', 'singleWord_crud.test.js')) -}) - -test('creates a multi word service file with CRUD actions', async () => { - const files = await service.files({ - name: 'UserProfile', - crud: true, - relations: null, +} + +const itCreatesASingleWordServiceTestFileWithCRUDActions = (baseArgs) => { + test('creates a service test file with CRUD actions', async () => { + const files = await service.files({ + ...baseArgs, + name: 'Post', + crud: true, + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/posts/posts.test.${extension}`] + ).toEqual( + loadGeneratorFixture('service', `singleWord_crud.test.${extension}`) + ) }) - - expect( - files['/path/to/project/api/src/services/userProfiles/userProfiles.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWord_crud.js')) -}) - -test('creates a multi word service test file with CRUD actions', async () => { - const files = await service.files({ - name: 'UserProfile', - crud: true, - relations: null, +} + +const itCreatesAMultiWordServiceFileWithCRUDActions = (baseArgs) => { + test('creates a multi word service file with CRUD actions', async () => { + const files = await service.files({ + ...baseArgs, + name: 'UserProfile', + crud: true, + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[ + `/path/to/project/api/src/services/userProfiles/userProfiles.${extension}` + ] + ).toEqual(loadGeneratorFixture('service', `multiWord_crud.${extension}`)) }) - - expect( - files['/path/to/project/api/src/services/userProfiles/userProfiles.test.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWord_crud.test.js')) -}) - -test('creates a single word service file with a hasMany relation', async () => { - const files = await service.files({ - name: 'User', - crud: false, - relations: ['userProfiles'], +} +const itCreatesAMultiWordServiceTestFileWithCRUDActions = (baseArgs) => { + test('creates a multi word service test file with CRUD actions', async () => { + const files = await service.files({ + ...baseArgs, + name: 'UserProfile', + crud: true, + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[ + `/path/to/project/api/src/services/userProfiles/userProfiles.test.${extension}` + ] + ).toEqual( + loadGeneratorFixture('service', `multiWord_crud.test.${extension}`) + ) }) - - expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( - loadGeneratorFixture('service', 'singleWord_hasMany.js') - ) -}) - -test('creates a single word service file with a belongsTo relation', async () => { - const files = await service.files({ - name: 'User', - crud: false, - relations: ['identity'], +} + +const itCreatesASingleWordServiceFileWithAHasManyRelation = (baseArgs) => { + test('creates a single word service file with a hasMany relation', async () => { + const files = await service.files({ + ...baseArgs, + name: 'User', + relations: ['userProfiles'], + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/users/users.${extension}`] + ).toEqual( + loadGeneratorFixture('service', `singleWord_hasMany.${extension}`) + ) }) - - expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( - loadGeneratorFixture('service', 'singleWord_belongsTo.js') - ) -}) - -test('creates a single word service file with multiple relations', async () => { - const files = await service.files({ - name: 'User', - crud: false, - relations: ['userProfiles', 'identity'], +} + +const itCreatesASingleWordServiceFileWithABelongsToRelation = (baseArgs) => { + test('creates a single word service file with a belongsTo relation', async () => { + const files = await service.files({ + ...baseArgs, + name: 'User', + relations: ['identity'], + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/users/users.${extension}`] + ).toEqual( + loadGeneratorFixture('service', `singleWord_belongsTo.${extension}`) + ) }) +} + +const itCreatesASingleWordServiceFileWithMultipleRelations = (baseArgs) => { + test('creates a single word service file with multiple relations', async () => { + const files = await service.files({ + ...baseArgs, + name: 'User', + relations: ['userProfiles', 'identity'], + }) + const extension = extensionForBaseArgs(baseArgs) + + expect( + files[`/path/to/project/api/src/services/users/users.${extension}`] + ).toEqual( + loadGeneratorFixture('service', `singleWord_multiple.${extension}`) + ) + }) +} + +describe('in javascript mode', () => { + const baseArgs = getDefaultArgs(service.builder) + + itReturnsExactly2Files(baseArgs) + itCreatesASingleWordServiceFile(baseArgs) + itCreatesASingleWordServiceTestFile(baseArgs) + itCreatesAMultiWordServiceFile(baseArgs) + itCreatesAMultiWordServiceTestFile(baseArgs) + itCreatesASingleWordServiceFileWithCRUDActions(baseArgs) + itCreatesASingleWordServiceTestFileWithCRUDActions(baseArgs) + itCreatesAMultiWordServiceFileWithCRUDActions(baseArgs) + itCreatesAMultiWordServiceTestFileWithCRUDActions(baseArgs) + itCreatesASingleWordServiceFileWithAHasManyRelation(baseArgs) + itCreatesASingleWordServiceFileWithABelongsToRelation(baseArgs) + itCreatesASingleWordServiceFileWithMultipleRelations(baseArgs) +}) - expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( - loadGeneratorFixture('service', 'singleWord_multiple.js') - ) +describe('in typescript mode', () => { + const baseArgs = { ...getDefaultArgs(service.builder), typescript: true } + + itReturnsExactly2Files(baseArgs) + itCreatesASingleWordServiceFile(baseArgs) + itCreatesASingleWordServiceTestFile(baseArgs) + itCreatesAMultiWordServiceFile(baseArgs) + itCreatesAMultiWordServiceTestFile(baseArgs) + itCreatesASingleWordServiceFileWithCRUDActions(baseArgs) + itCreatesASingleWordServiceTestFileWithCRUDActions(baseArgs) + itCreatesAMultiWordServiceFileWithCRUDActions(baseArgs) + itCreatesAMultiWordServiceTestFileWithCRUDActions(baseArgs) + itCreatesASingleWordServiceFileWithAHasManyRelation(baseArgs) + itCreatesASingleWordServiceFileWithABelongsToRelation(baseArgs) + itCreatesASingleWordServiceFileWithMultipleRelations(baseArgs) }) diff --git a/packages/cli/src/commands/generate/service/service.js b/packages/cli/src/commands/generate/service/service.js index 945ace05df2b..d4bb671d1e3c 100644 --- a/packages/cli/src/commands/generate/service/service.js +++ b/packages/cli/src/commands/generate/service/service.js @@ -1,28 +1,37 @@ import camelcase from 'camelcase' import pluralize from 'pluralize' +import { transformTSToJS } from '../../../lib' import { templateForComponentFile, createYargsForComponentGeneration, } from '../helpers' -export const files = async ({ name, relations, ...rest }) => { +export const files = async ({ + name, + relations, + javascript, + typescript, + ...rest +}) => { const componentName = camelcase(pluralize(name)) + const extension = 'ts' const serviceFile = templateForComponentFile({ name, componentName: componentName, + extension: `.${extension}`, apiPathSection: 'services', generator: 'service', - templatePath: 'service.js.template', + templatePath: `service.${extension}.template`, templateVars: { relations: relations || [], ...rest }, }) const testFile = templateForComponentFile({ name, componentName: componentName, - extension: '.test.js', + extension: `.test.${extension}`, apiPathSection: 'services', generator: 'service', - templatePath: 'test.js.template', + templatePath: `test.${extension}.template`, templateVars: { relations: relations || [], ...rest }, }) @@ -32,6 +41,11 @@ export const files = async ({ name, relations, ...rest }) => { // "path/to/fileB": "<<
id {post.id}
title {post.title}
slug {post.slug}
author {post.author}
body {post.body}
image {post.image}
postedAt {post.postedAt}