Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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