From c4f770c5100d16b38a8c21dc10d87b05c7a1bd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jim=C3=A9nez=20Rivera?= Date: Sun, 19 Oct 2025 12:27:13 +0200 Subject: [PATCH 1/3] Update CONTRIBUTING --- CONTRIBUTING.md | 88 ++++++++++++++++++++++++++++++++++++------------- docs/TESTING.md | 68 ++++++++++++++++++++++++++++++++------ 2 files changed, 124 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bff7d9b..ea8d238 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,43 @@ # Contributing +## Table of Contents + +- [Architecture](#architecture) +- [Code](#code) + - [File template](#file-template) + - [Testing](#testing) + - [Imports](#imports) + - [Use function instead of arrow functions by default](#use-function-instead-of-arrow-functions-by-default) + - [Use object props instead of multiple props](#use-object-props-instead-of-multiple-props) + - [Never use return types if the function infers the type correctly](#never-use-return-types-if-the-function-infers-the-type-correctly) + - [Logger](#logger) + - [Comments](#comments) + - [Frontend](#frontend) + ## Architecture ``` -backend -| core - | logger - | utils -| infra - | drive-server-wip - | sqlite - sqlite.module.ts - | services - function1.ts - function2.ts -| features - | backups - | sync - sync.module.ts - | services - function1.ts - function2.ts +📁 backend + 📁 core + 📁 logger + 📁 utils + 📁 infra + 📁 drive-server-wip + 📁 sqlite + 📄 sqlite.module.ts + 📁 services + 📄 function1.ts + 📄 function2.ts + 📁 features + 📁 backups + 📁 sync + 📄 sync.module.ts + 📁 services + 📄 function1.ts + 📄 function2.ts +📁 frontend + 📁 core + 📁 api ``` ## Code @@ -28,10 +45,7 @@ backend ### File template ```ts -type Props = { - prop1: A; - prop2: B; -}; +type Props = { prop1: A; prop2: B }; export function fn({ prop1, prop2 }: Props) {} ``` @@ -136,3 +150,33 @@ logger.debug({ * Whenever we change something, we should retain the comments from the previous version to see the history of the decision. */ ``` + +### Frontend + +We'll follow a structure similar to Angular's with services and components. The service will be a hook that manages all the logic. Both the service and the component will be stored in the same file. + +```ts +export function useComponent() { + const { t } = useI18n(); + const { data, status } = useCustomHook(); + + const value = useMemo(() => { + switch (status) { + case 'loading': + return t('loading'); + case 'error': + return ''; + case 'success': { + return data; + } + } + }, [status]); + + return { value }; +} + +export function Component() { + const { value } = useComponent(); + return

{value}

; +} +``` diff --git a/docs/TESTING.md b/docs/TESTING.md index 06b818f..8d885be 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,5 +1,20 @@ # Testing +## Table of Contents + +- [Types](#types) +- [Describe](#describe) +- [Mocks](#mocks) +- [Assert](#assert) +- [Structure](#structure) +- [Frontend](#frontend) + +## Types + +- Unit tests (`service.test.ts`). It just tests the service function inside the file and mocks all other functions. +- Infra tests (`anything.infra.test.ts`). It tests multiple functions and not only a service function. +- Long tests (`anything.long.test.ts`). It can test multiple functions or just one. However, we want to keep these tests in a separate runner so as not to block the main runner with slow tests. + ## Describe Use `name-of-file` in describe. Why? @@ -22,7 +37,7 @@ describe('name-of-file', () => { const depMock = partialSpyOn(depModule, 'dep'); beforeEach(() => { - depMock.mockReturnValue('first'); + depMock.mockReturnValue('value'); }); }); ``` @@ -36,12 +51,12 @@ describe('name-of-file', () => { const depMock = deepMocked(dep); beforeEach(() => { - depMock.mockReturnValue('first'); + depMock.mockReturnValue('value'); }); }); ``` -## Expect +## Assert To check the calls of a depMock we use `call` (just one call) or `calls` (0 or more calls). We use only `toHaveLength`, `toBe`, `toMatchObject` and `toStrictEqual` for assertions. @@ -59,9 +74,14 @@ describe('name-of-file', () => { expect(res).toBe(); expect(res).toMatchObject(); expect(res).toStrictEqual(); + + call(depMock).toBe(); call(depMock).toMatchObject(); + call(depMock).toStrictEqual(); + calls(depMock).toHaveLength(); calls(depMock).toMatchObject(); + calls(depMock).toStrictEqual(); }); }); ``` @@ -79,21 +99,49 @@ describe('name-of-file', () => { let props: Parameters[0]; beforeEach(() => { - dep1Mock.mockReturnValue('first'); - dep2Mock.mockReturnValue('first'); + dep1Mock.mockReturnValue('value1'); + dep2Mock.mockReturnValue('value1'); - props = mockProps({ prop: 'first' }); + props = mockProps({ prop: 'prop1' }); }); it('should do x when y', () => { // Given - dep1Mock.mockReturnValue('second'); - props.prop = 'second'; + dep1Mock.mockReturnValue('value2'); + props.prop = 'prop2'; // When const res = fn(props); // Then - expect(res).toMatchObject({ prop: 'result' }); - calls(dep1Mock).toMatchObject([{ prop: 'input' }]); + expect(res).toMatchObject([{ res: 'value2' }, { res: 'value1' }]); + call(dep1Mock).toStrictEqual([{ prop: 'prop2' }]); + }); +}); +``` + +## Frontend + +Testing the frontend is more complicated due to all the possible interactions we have. Therefore, to keep it as testable as possible and with as little code as possible, we'll only test the component logic (service). + +```ts +import { renderHook } from '@testing-library/react-hooks'; +import * as depModule from 'module'; + +describe('name-of-file', () => { + const depMock = partialSpyOn(depModule, 'dep'); + + it('should do x when y', () => { + // Given + depMock.mockReturnValue('value1'); + // When + const { result, rerender } = renderHook(() => useComponent()); + // Then + expect(result.current.value).toBe('value1'); + // Given + depMock.mockReturnValue('value2'); + // When + rerender(); + // Then + expect(result.current.value).toBe('value2'); }); }); ``` From a299aff829e03a16545fd27061767d980aa74d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jim=C3=A9nez=20Rivera?= Date: Mon, 20 Oct 2025 19:54:49 +0200 Subject: [PATCH 2/3] Update check-pr-size.yml --- .github/workflows/check-pr-size.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check-pr-size.yml b/.github/workflows/check-pr-size.yml index 5b62fb2..8b730e0 100644 --- a/.github/workflows/check-pr-size.yml +++ b/.github/workflows/check-pr-size.yml @@ -6,7 +6,6 @@ on: jobs: check_pr_size: - if: false runs-on: ubuntu-latest timeout-minutes: 1 From 78ddc8720d53470359c5551377fb96300c3da471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jim=C3=A9nez=20Rivera?= Date: Wed, 22 Oct 2025 20:17:39 +0200 Subject: [PATCH 3/3] Update TESTING.md --- docs/TESTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/TESTING.md b/docs/TESTING.md index 8d885be..a3bc98e 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -12,8 +12,7 @@ ## Types - Unit tests (`service.test.ts`). It just tests the service function inside the file and mocks all other functions. -- Infra tests (`anything.infra.test.ts`). It tests multiple functions and not only a service function. -- Long tests (`anything.long.test.ts`). It can test multiple functions or just one. However, we want to keep these tests in a separate runner so as not to block the main runner with slow tests. +- Infra tests (`anything.infra.test.ts`). It tests multiple functions and not only a service function or it is a long test. We want to keep these tests in a separate runner so as not to block the main runner with slow tests. ## Describe