Skip to main content

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; use unknown if 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-testid attributes for selectors
  • Test behavior, not implementation
  • Keep tests independent

Don't:

  • Use cy.wait(3000) or page.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

  1. 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
}
}
}
`;
  1. Run codegen to generate types
  2. 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