Skip to content
Closed
18 changes: 9 additions & 9 deletions clean-architecture-visualizer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ npm install -g clean-architecture-visualizer
## Usage

```bash
cave init # scaffold a new CSC207-style CA project
cave module_init # scaffold a new project, organized by feature module
cave usecase <name> # generate the boilerplate for a new use case
cave feature <feature> # add a new feature to the project
cave module_usecase <feature> <usecase> # add a use case to an existing feature
cave verify # check that use cases follow Clean Architecture rules
cave start # start the backend server and frontend visualizer
cave start --backend-only # start only the backend server
cave end # stop the server and clean up temp files
cave init <language> # scaffold a new CSC207-style CA project in the chosen programming language
cave module_init <language> # scaffold a new project, organized by feature module in the chosen language
cave usecase <name> # generate the boilerplate for a new use case
cave feature <feature> # add a new feature to the project
cave module_usecase <feature> <usecase> # add a use case to an existing feature
cave verify # check that use cases follow Clean Architecture rules
cave start # start the backend server and frontend visualizer
cave start --backend-only # start only the backend server
cave end # stop the server and clean up temp files
```

`cave start` launches a local web UI for browsing use case diagrams and CA layer violations in your project.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const useGenerateProject = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: generateProject,
mutationFn: (language: string) => generateProject(language),
onSuccess: () => {
// invalidate data here
queryClient.invalidateQueries({ queryKey: ['file-tree'] });
Expand All @@ -23,7 +23,7 @@ export const useGenerateModuleProject = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: generateModuleProject,
mutationFn: (language: string) => generateModuleProject(language),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['file-tree'] });
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import apiClient from './apiClient';
/**
* Initiates the base folder structure for a Clean Architecture project.
*/
export const generateProject = async () => {
export const generateProject = async (language: string) => {
const { data } = await apiClient.post<{ message: string }>(
'/template/generate'
`/template/generate/${encodeURIComponent(language)}`
);
return data;
};

/**
* Initiates the base folder structure for a Clean Architecture project packaged by module.
*/
export const generateModuleProject = async () => {
export const generateModuleProject = async (language: string) => {
const { data } = await apiClient.post<{ message: string }>(
'/template/module_generate'
`/template/module_generate/${encodeURIComponent(language)}`
);
return data;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ const ProjectStarter = () => {

const handleCloseSnackbar = () => setSnackbar({ ...snackbar, open: false });

// The 'java' is temporary until an option to select programming language is added
const handleCreateProject = () => {
triggerGenerate(undefined, {
triggerGenerate('java', {
onSuccess: (data) => {
setSnackbar({
open: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ describe('Template Hooks', () => {
vi.restoreAllMocks();
});

it('useGenerateProject resolves the template response and invalidates file-tree queries', async () => {
it('useGenerateProject resolves the template response and invalidates file-tree queries for java', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post('*/api/template/generate', () => {
http.post(`*/api/template/generate/${encodeURIComponent('java')}`, () => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
Expand All @@ -32,30 +32,189 @@ describe('Template Hooks', () => {

const { result } = renderHook(() => useGenerateProject());

result.current.mutate();
result.current.mutate('java');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateModuleProject resolves the template response and invalidates file-tree queries', async () => {
it('useGenerateProject resolves the template response and invalidates file-tree queries for python', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post('*/api/template/module_generate', () => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
})
http.post(
`*/api/template/generate/${encodeURIComponent('python')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateProject());

result.current.mutate('python');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateProject resolves the template response and invalidates file-tree queries for javascript', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post(
`*/api/template/generate/${encodeURIComponent('javascript')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateProject());

result.current.mutate('javascript');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateProject resolves the template response and invalidates file-tree queries for typescript', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post(
`*/api/template/generate/${encodeURIComponent('typescript')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateProject());

result.current.mutate('typescript');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateModuleProject resolves the template response and invalidates file-tree queries for java', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post(
`*/api/template/module_generate/${encodeURIComponent('java')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateModuleProject());

result.current.mutate('java');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateModuleProject resolves the template response and invalidates file-tree queries for python', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post(
`*/api/template/module_generate/${encodeURIComponent('python')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateModuleProject());

result.current.mutate('python');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateModuleProject resolves the template response and invalidates file-tree queries for javascript', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post(
`*/api/template/module_generate/${encodeURIComponent('javascript')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateModuleProject());

result.current.mutate('javascript');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-tree'] });
});

it('useGenerateModuleProject resolves the template response and invalidates file-tree queries for typescript', async () => {
const invalidateSpy = vi
.spyOn(QueryClient.prototype, 'invalidateQueries')
.mockResolvedValue(undefined);

server.use(
http.post(
`*/api/template/module_generate/${encodeURIComponent('typescript')}`,
() => {
return HttpResponse.json(
{ message: 'Project initiated successfully' },
{ status: 201 }
);
}
)
);

const { result } = renderHook(() => useGenerateModuleProject());

result.current.mutate();
result.current.mutate('typescript');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.message).toBe('Project initiated successfully');
Expand Down
Loading
Loading