Contributing Guide
This guide covers the complete setup process for contributing to Multiforum.
Prerequisites
- Node.js 26.x
- npm (included with Node.js)
- Git
- Docker Engine or Docker Desktop with Docker Compose v2
Repository Setup
Clone the Frontend Repository
git clone https://github.com/gennit-project/multiforum-nuxt.git
cd multiforum-nuxt
npm install
Start the Source-Built Stack
The normal Compose file pulls official images. Add the contributor override to build the frontend from the current checkout and the backend from its Git repository:
docker compose \
--env-file .env.quickstart.example \
-f docker-compose.yml \
-f docker-compose.source.yml \
up --build
This uses local-development authentication and automatic database bootstrap, so Auth0, GCS, maps, and email are not prerequisites. Open http://localhost:3000.
Set MULTIFORUM_BACKEND_REPOSITORY and MULTIFORUM_BACKEND_REF when you need
to build a backend fork or feature branch. For a two-checkout hot-reload setup,
follow each repository's README and copy its current .env.example; do not use
the removed browser-side VITE_AUTH0_* variables.
Testing
Unit Tests (Frontend)
cd multiforum-nuxt
npm run test:unit
Uses Vitest with Vue Test Utils.
E2E Tests (Frontend)
cd multiforum-nuxt
npm run test:playwright
Uses Playwright for end-to-end testing.
Running Specific Tests
# Run a specific test file
npm run test:unit -- src/utils/permissionUtils.spec.ts
# Run tests matching a pattern
npm run test:unit -- -t "permission"
Code Style
TypeScript
- Strict mode enabled
- Explicit types for function parameters and returns
- Use interfaces for object shapes
- Avoid
any; useunknownif needed
// Good
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Avoid
function calculateTotal(items: any): any {
return items.reduce((sum: any, item: any) => sum + item.price, 0);
}
Vue Components
Use <script setup> with TypeScript:
<script setup lang="ts">
interface Props {
title: string;
count?: number;
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
});
const emit = defineEmits<{
(e: "update", value: number): void;
}>();
</script>
<template>
<div>
<h1>{{ title }}</h1>
<span>{{ count }}</span>
</div>
</template>
Composables
Extract reusable logic into composables:
// composables/useCounter.ts
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, increment, decrement };
}
Testing Patterns
Unit Tests
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import MyComponent from "@/components/MyComponent.vue";
describe("MyComponent", () => {
it("renders correctly with default props", () => {
const wrapper = mount(MyComponent);
expect(wrapper.exists()).toBe(true);
});
it("emits event when clicked", async () => {
const wrapper = mount(MyComponent);
await wrapper.find("button").trigger("click");
expect(wrapper.emitted()).toHaveProperty("click");
});
});
E2E Tests
import { test, expect } from "@playwright/test";
test("user can create a discussion", async ({ page }) => {
// Navigate
await page.goto("/forums/test-channel");
// Create discussion
await page.click('[data-testid="create-discussion-button"]');
await page.fill('[data-testid="title-input"]', "Test Discussion");
await page.fill('[data-testid="body-input"]', "Test content");
await page.click('[data-testid="save-button"]');
// Verify
await expect(page.locator('[data-testid="discussion-title"]'))
.toContainText("Test Discussion");
});
Test Best Practices
Do:
- Wait for network requests instead of arbitrary timeouts
- Use
data-testidattributes for selectors - Test behavior, not implementation
- Keep tests independent
Don't:
- Use
cy.wait(3000)orpage.waitForTimeout(3000) - Select by CSS classes that may change
- Test internal component state
- Create tests that depend on each other
GraphQL Development
Generating Types
After schema changes:
cd multiforum-nuxt
npm run codegen
This generates TypeScript types from the GraphQL schema.
Adding Queries/Mutations
- Add the GraphQL operation to
graphQLData/:
// graphQLData/discussion/queries.ts
export const GET_DISCUSSION = gql`
query GetDiscussion($id: ID!) {
discussions(where: { id: $id }) {
id
title
body
Author {
username
}
}
}
`;
- Run codegen to generate types
- Use in components with Apollo
Backend Resolvers
Custom resolvers go in customResolvers/:
// customResolvers/mutations/createDiscussion.ts
export const createDiscussion = async (
_root: any,
args: { input: DiscussionInput },
context: Context
) => {
const { ogm, driver } = context;
// Validate input
// Create in database
// Return result
};
Pull Request Checklist
Before submitting:
- Code follows style guidelines
- TypeScript compiles without errors (
npm run tsc) - Unit tests pass (
npm run test:unit) - E2E tests pass (
npm run test:playwright) - Linting passes (
npm run lint) - Documentation updated if needed
- Commit messages are clear
Husky Pre-commit Hooks
The project uses Husky for pre-commit checks:
- TypeScript compilation
- Unit tests
- Linting
If commits fail, fix the issues before committing.
Common Issues
Neo4j Connection Failed
- Ensure Neo4j is running
- Check credentials in
.env - Verify port 7687 is accessible
Auth0 Errors
- Verify callback URLs in Auth0 dashboard
- Check client ID and secret
- Ensure audience matches API identifier
Type Errors After Schema Changes
- Run
npm run codegen - Restart the TypeScript language server
- Check for breaking schema changes
Getting Help
- Check existing documentation
- Search closed issues and PRs
- Open a discussion for questions
- Contact: catherine.luse@gmail.com