refactor: project structure

This commit is contained in:
Théo LUDWIG 2024-03-16 00:36:44 +01:00
parent 164b024e1f
commit bfaada5b4f
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
60 changed files with 135 additions and 306 deletions

View File

@ -15,10 +15,9 @@ Un tracker d'habitudes pour performer au boulot et dans la vie de tous les jours
### Documentation ### Documentation
- [Sujet du projet](./docs/SUJET.md) - [Sujet](./docs/SUJET.md) + [Cahier des charges](./docs/CAHIER-DES-CHARGES.md)
- [Cahier des charges](./docs/CAHIER-DES-CHARGES.md)
- [Modèle Logique des Données (MLD)](./docs/MLD.md) - [Modèle Logique des Données (MLD)](./docs/MLD.md)
- [Clean Architecture](./docs/ARCHITECTURE.md) - [Architecture](./docs/ARCHITECTURE.md)
- [Conventions développement informatique](./docs/CONVENTIONS.md) - [Conventions développement informatique](./docs/CONVENTIONS.md)
#### Principaux Outils Informatiques Utilisés #### Principaux Outils Informatiques Utilisés
@ -26,8 +25,10 @@ Un tracker d'habitudes pour performer au boulot et dans la vie de tous les jours
- [React Native](https://reactnative.dev/) + [Expo](https://expo.io/): Framework pour le développement d'applications mobiles. - [React Native](https://reactnative.dev/) + [Expo](https://expo.io/): Framework pour le développement d'applications mobiles.
- [TypeScript](https://www.typescriptlang.org/): Langage de programmation. - [TypeScript](https://www.typescriptlang.org/): Langage de programmation.
- [React Native Paper](https://callstack.github.io/react-native-paper/): Bibliothèque de composants pour React Native. - [React Native Paper](https://callstack.github.io/react-native-paper/): Bibliothèque de composants pour React Native.
<!-- - [WatermelonDB](https://nozbe.github.io/WatermelonDB/): Base de données locale, pour permettre une utilisation hors-ligne de l'application. -->
- [Supabase](https://supabase.io/): Backend, serveur d'API pour le stockage des données. - [Supabase](https://supabase.io/): Backend, serveur d'API pour le stockage des données.
<!--
- [WatermelonDB](https://nozbe.github.io/WatermelonDB/): Base de données locale, pour permettre une utilisation hors-ligne de l'application.
-->
## Développement du projet en local ## Développement du projet en local

View File

@ -4,11 +4,11 @@
"slug": "p61-project", "slug": "p61-project",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/images/icon.png", "icon": "./presentation/assets/images/icon.png",
"scheme": "p61-project", "scheme": "p61-project",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"splash": { "splash": {
"image": "./assets/images/splashscreen.jpg", "image": "./presentation/assets/images/splashscreen.jpg",
"resizeMode": "cover", "resizeMode": "cover",
"backgroundColor": "#74b6cb" "backgroundColor": "#74b6cb"
}, },
@ -18,14 +18,14 @@
}, },
"android": { "android": {
"adaptiveIcon": { "adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png", "foregroundImage": "./presentation/assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
} }
}, },
"web": { "web": {
"bundler": "metro", "bundler": "metro",
"output": "static", "output": "static",
"favicon": "./assets/images/favicon.png" "favicon": "./presentation/assets/images/favicon.png"
}, },
"plugins": ["expo-router"], "plugins": ["expo-router"],
"experiments": { "experiments": {

View File

@ -1,16 +1,14 @@
import { StyleSheet, Text, View } from "react-native" import { StyleSheet, Text, View } from "react-native"
import { SafeAreaView } from "react-native-safe-area-context" import { SafeAreaView } from "react-native-safe-area-context"
import { useHabitsTracker } from "@/contexts/HabitsTracker" import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
const HabitsPage: React.FC = () => { const HabitsPage: React.FC = () => {
const { habitsTrackerPresenterState } = useHabitsTracker() const { habitsTracker } = useHabitsTracker()
const { habitsTracker } = habitsTrackerPresenterState
const { habitProgressHistories } = habitsTracker
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
{habitProgressHistories.map((progressHistory) => { {habitsTracker.habitsHistory.map((progressHistory) => {
const { habit } = progressHistory const { habit } = progressHistory
return ( return (

View File

@ -8,10 +8,10 @@ import {
} from "react-native-paper" } from "react-native-paper"
import { StatusBar } from "expo-status-bar" import { StatusBar } from "expo-status-bar"
import CanterburyFont from "../assets/fonts/Canterbury.ttf" import CanterburyFont from "../presentation/assets/fonts/Canterbury.ttf"
import GeoramFont from "../assets/fonts/Georama-Black.ttf" import GeoramFont from "../presentation/assets/fonts/Georama-Black.ttf"
import SpaceMonoFont from "../assets/fonts/SpaceMono-Regular.ttf" import SpaceMonoFont from "../presentation/assets/fonts/SpaceMono-Regular.ttf"
import { HabitsTrackerProvider } from "@/contexts/HabitsTracker" import { HabitsTrackerProvider } from "@/presentation/react/contexts/HabitsTracker"
export { ErrorBoundary } from "expo-router" export { ErrorBoundary } from "expo-router"

View File

@ -1,18 +0,0 @@
import type { Habit } from "./Habit"
import type { HabitProgress } from "./HabitProgress"
export interface HabitProgressHistoryOptions {
habit: Habit
habitProgresses: HabitProgress[]
}
export class HabitProgressHistory implements HabitProgressHistoryOptions {
public habit: Habit
public habitProgresses: HabitProgress[]
public constructor(options: HabitProgressHistoryOptions) {
const { habit, habitProgresses } = options
this.habit = habit
this.habitProgresses = habitProgresses
}
}

View File

@ -1,18 +0,0 @@
import type { HabitProgressHistory } from "./HabitProgressHistory"
export interface HabitsTrackerOptions {
habitProgressHistories: HabitProgressHistory[]
}
export class HabitsTracker implements HabitsTrackerOptions {
public habitProgressHistories: HabitProgressHistory[]
public constructor(options: HabitsTrackerOptions) {
const { habitProgressHistories } = options
this.habitProgressHistories = habitProgressHistories
}
public static default(): HabitsTracker {
return new HabitsTracker({ habitProgressHistories: [] })
}
}

View File

@ -1,10 +0,0 @@
import type { Habit } from "../entities/Habit"
import type { HabitProgress } from "../entities/HabitProgress"
export interface GetHabitProgressesOptions {
habit: Habit
}
export interface GetHabitProgressesRepository {
execute: (options: GetHabitProgressesOptions) => Promise<HabitProgress[]>
}

View File

@ -51,4 +51,4 @@ Vous serez évalués sur les aspects suivants :
## Exemple dinterfaces ## Exemple dinterfaces
![UI Example](../assets/images/ui-example.png) ![UI Example](../presentation/assets/images/ui-example.png)

View File

@ -22,15 +22,17 @@ Une pipeline CI ([`.gitlab-ci.yml`](.gitlab-ci.yml)) est en place pour vérifier
Le projet suit la convention [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) reposant sur 2 branches principales: Le projet suit la convention [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) reposant sur 2 branches principales:
- `main` (ou `master`): Contient le code de la dernière version stable et déployé en production. - `main`: Contient le code de la dernière version stable et déployé en production.
- `develop`: Contient le code en cours de développement. Les nouvelles fonctionnalités et les correctifs de bugs sont fusionnés ici. - `develop`: Contient le code en cours de développement. Les nouvelles fonctionnalités et les correctifs de bugs sont fusionnés ici.
Chaque nouvelle fonctionnalité ou correctif de bug est développé dans une branche dédiée à partir de `develop`, nommée `feat/<nom-de-la-fonctionnalité>` ou `fix/<nom-du-bug>`. Une fois le développement terminé, une merge request est créée pour demander une revue de code, et une fois validée, la branche est fusionnée dans `develop`, puis supprimée.
## Convention des commits ## Convention des commits
Les commits respectent la convention [Conventional Commits](https://www.conventionalcommits.org/) et [Semantic Versioning](https://semver.org/) pour la gestion des versions et des releases en fonction des commits. Les commits respectent la convention [Conventional Commits](https://www.conventionalcommits.org/) et [Semantic Versioning](https://semver.org/) pour la gestion des versions et des releases en fonction des commits.
Les commits doivent être **atomiques** c'est à dire qu'il respecte 3 règles: Les commits doivent être **atomiques** c'est à dire qu'il respecte 3 règles:
- Ne concerne qu'un seul sujet (une fonctionnalité, une correction de bug, etc.) - Ne concerne qu'un seul sujet (une fonctionnalité, une correction de bug, etc.).
- Doit avoir un message clair et concis - Doit avoir un message clair et concis.
- Ne doit pas rendre de dépôt "incohérent" (bloque la CI du projet) - Ne doit pas rendre de dépôt "incohérent" (bloque la CI du projet).

View File

@ -0,0 +1,18 @@
import type { Habit } from "./Habit"
import type { HabitProgress } from "./HabitProgress"
export interface HabitHistoryOptions {
habit: Habit
progressHistory: HabitProgress[]
}
export class HabitHistory implements HabitHistoryOptions {
public habit: Habit
public progressHistory: HabitProgress[]
public constructor(options: HabitHistoryOptions) {
const { habit, progressHistory } = options
this.habit = habit
this.progressHistory = progressHistory
}
}

View File

@ -0,0 +1,18 @@
import type { HabitHistory } from "./HabitHistory"
export interface HabitsTrackerOptions {
habitsHistory: HabitHistory[]
}
export class HabitsTracker implements HabitsTrackerOptions {
public habitsHistory: HabitHistory[]
public constructor(options: HabitsTrackerOptions) {
const { habitsHistory } = options
this.habitsHistory = habitsHistory
}
public static default(): HabitsTracker {
return new HabitsTracker({ habitsHistory: [] })
}
}

View File

@ -0,0 +1,10 @@
import type { Habit } from "../entities/Habit"
import type { HabitProgress } from "../entities/HabitProgress"
export interface GetHabitProgressHistoryOptions {
habit: Habit
}
export interface GetHabitProgressHistoryRepository {
execute: (options: GetHabitProgressHistoryOptions) => Promise<HabitProgress[]>
}

View File

@ -1,12 +1,12 @@
import { HabitProgressHistory } from "../entities/HabitProgressHistory" import { HabitHistory } from "../entities/HabitHistory"
import { HabitsTracker } from "../entities/HabitsTracker" import { HabitsTracker } from "../entities/HabitsTracker"
import type { User } from "../entities/User" import type { User } from "../entities/User"
import type { GetHabitProgressesRepository } from "../repositories/GetHabitProgresses" import type { GetHabitProgressHistoryRepository } from "../repositories/GetHabitProgressHistory"
import type { GetHabitsByUserIdRepository } from "../repositories/GetHabitsByUserId" import type { GetHabitsByUserIdRepository } from "../repositories/GetHabitsByUserId"
export interface RetrieveHabitsTrackerUseCaseDependencyOptions { export interface RetrieveHabitsTrackerUseCaseDependencyOptions {
getHabitsByUserIdRepository: GetHabitsByUserIdRepository getHabitsByUserIdRepository: GetHabitsByUserIdRepository
getHabitProgressesRepository: GetHabitProgressesRepository getHabitProgressHistoryRepository: GetHabitProgressHistoryRepository
} }
export interface RetrieveHabitsTrackerUseCaseOptions { export interface RetrieveHabitsTrackerUseCaseOptions {
@ -17,11 +17,12 @@ export class RetrieveHabitsTrackerUseCase
implements RetrieveHabitsTrackerUseCaseDependencyOptions implements RetrieveHabitsTrackerUseCaseDependencyOptions
{ {
public getHabitsByUserIdRepository: GetHabitsByUserIdRepository public getHabitsByUserIdRepository: GetHabitsByUserIdRepository
public getHabitProgressesRepository: GetHabitProgressesRepository public getHabitProgressHistoryRepository: GetHabitProgressHistoryRepository
public constructor(options: RetrieveHabitsTrackerUseCaseDependencyOptions) { public constructor(options: RetrieveHabitsTrackerUseCaseDependencyOptions) {
this.getHabitsByUserIdRepository = options.getHabitsByUserIdRepository this.getHabitsByUserIdRepository = options.getHabitsByUserIdRepository
this.getHabitProgressesRepository = options.getHabitProgressesRepository this.getHabitProgressHistoryRepository =
options.getHabitProgressHistoryRepository
} }
public async execute( public async execute(
@ -31,15 +32,16 @@ export class RetrieveHabitsTrackerUseCase
const habits = await this.getHabitsByUserIdRepository.execute({ userId }) const habits = await this.getHabitsByUserIdRepository.execute({ userId })
const habitProgressHistories = await Promise.all( const habitProgressHistories = await Promise.all(
habits.map(async (habit) => { habits.map(async (habit) => {
const habitProgresses = await this.getHabitProgressesRepository.execute( const progressHistory =
{ await this.getHabitProgressHistoryRepository.execute({
habit, habit,
}, })
) return new HabitHistory({ habit, progressHistory })
return new HabitProgressHistory({ habit, habitProgresses })
}), }),
) )
const habitsTracker = new HabitsTracker({ habitProgressHistories }) const habitsTracker = new HabitsTracker({
habitsHistory: habitProgressHistories,
})
return habitsTracker return habitsTracker
} }
} }

View File

@ -1,93 +0,0 @@
import { act, renderHook, waitFor } from "@testing-library/react-native"
import AsyncStorage from "@react-native-async-storage/async-storage"
import { useLocalStorage } from "@/hooks/useLocalStorage"
describe("hooks/useLocalStorage", () => {
beforeEach(async () => {
jest.clearAllMocks()
await AsyncStorage.clear()
})
it("should get the default value", () => {
const key = "key"
const givenDefaultValue = { key: "value" }
const { result } = renderHook(() => {
return useLocalStorage(key, givenDefaultValue)
})
const [actualValue] = result.current
expect(actualValue).toEqual(givenDefaultValue)
})
it("should get the value from the storage", async () => {
const key = "key"
const givenDefaultValue = { someValue: "value" }
const storedValue = { someValue: "value" }
const { result } = renderHook(() => {
return useLocalStorage(key, givenDefaultValue)
})
await waitFor(() => {
expect(AsyncStorage.getItem).toHaveBeenCalledWith(key)
})
const [actualValue] = result.current
expect(actualValue).toEqual(storedValue)
})
it("should set the value to the storage", async () => {
const key = "key"
const givenDefaultValue = { someValue: "value" }
const storedValue = { someValue: "value" }
const { result } = renderHook(() => {
return useLocalStorage(key, givenDefaultValue)
})
const [, setValue] = result.current
await act(() => {
setValue(storedValue)
})
await waitFor(() => {
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
key,
JSON.stringify(storedValue),
)
})
})
it("should get default value if storage value is not valid JSON", async () => {
console.error = jest.fn()
const key = "key"
const givenDefaultValue = { someValue: "value" }
const storedValue = "{not valid JSON"
await AsyncStorage.setItem(key, storedValue)
const { result } = renderHook(() => {
return useLocalStorage(key, givenDefaultValue)
})
await waitFor(() => {
expect(AsyncStorage.getItem).toHaveBeenCalledWith(key)
})
const [actualValue] = result.current
expect(actualValue).toEqual(givenDefaultValue)
})
it("should catch the error when setting the value to the storage", async () => {
console.error = jest.fn()
const key = "key"
const givenDefaultValue = { someValue: "value" }
const storedValue = { someValue: "value" }
const error = new Error("error")
;(AsyncStorage.setItem as jest.Mock).mockRejectedValue(error)
const { result } = renderHook(() => {
return useLocalStorage(key, givenDefaultValue)
})
const [, setValue] = result.current
await act(() => {
setValue(storedValue)
})
await waitFor(() => {
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
key,
JSON.stringify(storedValue),
)
})
expect(console.error).toHaveBeenCalledWith(error)
})
})

View File

@ -1,43 +0,0 @@
import { useCallback, useEffect, useRef, useState } from "react"
import AsyncStorage from "@react-native-async-storage/async-storage"
type UseLocalStorageResult<T> = [T, React.Dispatch<React.SetStateAction<T>>]
export const useLocalStorage = <T extends unknown>(
key: string,
defaultValue: T,
): UseLocalStorageResult<T> => {
const hasSaved = useRef(false)
const [value, setValue] = useState<T>(defaultValue)
const getFromLocalStorage = useCallback(async (): Promise<T> => {
const value = await AsyncStorage.getItem(key)
hasSaved.current = true
if (value == null) {
return defaultValue
}
return JSON.parse(value) as T
}, [key, defaultValue])
useEffect(() => {
if (!hasSaved.current) {
return
}
AsyncStorage.setItem(key, JSON.stringify(value)).catch((error) => {
console.error(error)
})
}, [key, value])
useEffect(() => {
getFromLocalStorage()
.then((value) => {
setValue(value)
})
.catch((error) => {
setValue(defaultValue)
console.error(error)
})
}, [defaultValue, getFromLocalStorage])
return [value, setValue]
}

View File

@ -1,17 +1,14 @@
// export const taskRepository = new TaskLocalStorageRepository()
// export const taskService = new TaskService(taskRepository)
// export const taskPresenter = new TaskPresenter(taskService)
import { RetrieveHabitsTrackerUseCase } from "../domain/use-cases/RetrieveHabitsTracker" import { RetrieveHabitsTrackerUseCase } from "../domain/use-cases/RetrieveHabitsTracker"
import { HabitsTrackerPresenter } from "./presenters/HabitsTrackerPresenter" import { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
import { GetHabitProgressesSupabaseRepository } from "./repositories/supabase/lib/GetHabitProgresses" import { GetHabitProgressHistorySupabaseRepository } from "./repositories/supabase/lib/GetHabitProgressHistory"
import { GetHabitsByUserIdSupabaseRepository } from "./repositories/supabase/lib/GetHabitsByUserId" import { GetHabitsByUserIdSupabaseRepository } from "./repositories/supabase/lib/GetHabitsByUserId"
import { supabaseClient } from "./repositories/supabase/supabase" import { supabaseClient } from "./repositories/supabase/supabase"
/** /**
* Repositories * Repositories
*/ */
const getHabitProgressesRepository = new GetHabitProgressesSupabaseRepository({ const getHabitProgressesRepository =
new GetHabitProgressHistorySupabaseRepository({
supabaseClient, supabaseClient,
}) })
const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({ const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
@ -22,7 +19,7 @@ const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
* Use Cases * Use Cases
*/ */
const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({ const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
getHabitProgressesRepository, getHabitProgressHistoryRepository: getHabitProgressesRepository,
getHabitsByUserIdRepository, getHabitsByUserIdRepository,
}) })

View File

@ -1,17 +1,17 @@
import type { GetHabitProgressesRepository } from "@/data/domain/repositories/GetHabitProgresses" import type { GetHabitProgressHistoryRepository } from "@/domain/repositories/GetHabitProgressHistory"
import { SupabaseRepository } from "./_SupabaseRepository" import { SupabaseRepository } from "./_SupabaseRepository"
import { HabitProgress } from "@/data/domain/entities/HabitProgress" import { HabitProgress } from "@/domain/entities/HabitProgress"
import type { GoalProgress } from "@/data/domain/entities/Goal" import type { GoalProgress } from "@/domain/entities/Goal"
import { import {
GoalBooleanProgress, GoalBooleanProgress,
GoalNumericProgress, GoalNumericProgress,
} from "@/data/domain/entities/Goal" } from "@/domain/entities/Goal"
export class GetHabitProgressesSupabaseRepository export class GetHabitProgressHistorySupabaseRepository
extends SupabaseRepository extends SupabaseRepository
implements GetHabitProgressesRepository implements GetHabitProgressHistoryRepository
{ {
execute: GetHabitProgressesRepository["execute"] = async (options) => { execute: GetHabitProgressHistoryRepository["execute"] = async (options) => {
const { habit } = options const { habit } = options
const { data, error } = await this.supabaseClient const { data, error } = await this.supabaseClient
.from("habits_progresses") .from("habits_progresses")
@ -20,7 +20,7 @@ export class GetHabitProgressesSupabaseRepository
if (error != null) { if (error != null) {
throw new Error(error.message) throw new Error(error.message)
} }
const habitProgresses = data.map((item) => { const habitProgressHistory = data.map((item) => {
let goalProgress: GoalProgress | null = null let goalProgress: GoalProgress | null = null
if (habit.goal.isNumeric()) { if (habit.goal.isNumeric()) {
goalProgress = new GoalNumericProgress({ goalProgress = new GoalNumericProgress({
@ -44,6 +44,6 @@ export class GetHabitProgressesSupabaseRepository
}) })
return habitProgress return habitProgress
}) })
return habitProgresses return habitProgressHistory
} }
} }

View File

@ -1,8 +1,8 @@
import type { GetHabitsByUserIdRepository } from "@/data/domain/repositories/GetHabitsByUserId" import type { GetHabitsByUserIdRepository } from "@/domain/repositories/GetHabitsByUserId"
import { SupabaseRepository } from "./_SupabaseRepository" import { SupabaseRepository } from "./_SupabaseRepository"
import { Habit } from "@/data/domain/entities/Habit" import { Habit } from "@/domain/entities/Habit"
import type { Goal } from "@/data/domain/entities/Goal" import type { Goal } from "@/domain/entities/Goal"
import { GoalBoolean, GoalNumeric } from "@/data/domain/entities/Goal" import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
export class GetHabitsByUserIdSupabaseRepository export class GetHabitsByUserIdSupabaseRepository
extends SupabaseRepository extends SupabaseRepository

View File

@ -1,7 +1,7 @@
{ {
"preset": "jest-expo", "preset": "jest-expo",
"roots": ["./"], "roots": ["./"],
"setupFilesAfterEnv": ["<rootDir>/tests/jest.setup.ts"], "setupFilesAfterEnv": ["@testing-library/react-native/extend-expect"],
"fakeTimers": { "fakeTimers": {
"enableGlobally": true "enableGlobally": true
}, },
@ -10,7 +10,7 @@
"coverageReporters": ["text", "text-summary", "cobertura"], "coverageReporters": ["text", "text-summary", "cobertura"],
"collectCoverageFrom": [ "collectCoverageFrom": [
"<rootDir>/**/*.{ts,tsx}", "<rootDir>/**/*.{ts,tsx}",
"!<rootDir>/components/ExternalLink.tsx", "!<rootDir>/presentation/react/components/ExternalLink.tsx",
"!<rootDir>/.expo", "!<rootDir>/.expo",
"!<rootDir>/app/+html.tsx", "!<rootDir>/app/+html.tsx",
"!<rootDir>/app/**/_layout.tsx", "!<rootDir>/app/**/_layout.tsx",

59
package-lock.json generated
View File

@ -10,7 +10,6 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@expo/vector-icons": "14.0.0", "@expo/vector-icons": "14.0.0",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-navigation/native": "6.1.16", "@react-navigation/native": "6.1.16",
"@supabase/supabase-js": "2.39.8", "@supabase/supabase-js": "2.39.8",
"expo": "50.0.13", "expo": "50.0.13",
@ -34,7 +33,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.24.0", "@babel/core": "7.24.0",
"@commitlint/cli": "19.1.0", "@commitlint/cli": "19.2.0",
"@commitlint/config-conventional": "19.1.0", "@commitlint/config-conventional": "19.1.0",
"@testing-library/react-native": "12.4.3", "@testing-library/react-native": "12.4.3",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
@ -51,7 +50,7 @@
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.34.0", "eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-react-native": "4.1.0", "eslint-plugin-react-native": "4.1.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "51.0.1",
@ -2122,15 +2121,15 @@
} }
}, },
"node_modules/@commitlint/cli": { "node_modules/@commitlint/cli": {
"version": "19.1.0", "version": "19.2.0",
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.1.0.tgz", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.2.0.tgz",
"integrity": "sha512-SYGm8HGbVzrlSYeB6oo6pG1Ec6bOMJcDsXgNGa4vgZQsPj6nJkcbTWlIRmtmIk0tHi0d5sCljGuQ+g/0NCPv7w==", "integrity": "sha512-8XnQDMyQR+1/ldbmIyhonvnDS2enEw48Wompo/967fsEvy9Vj5/JbDutzmSBKxANWDVeEbR9QQm0yHpw6ArrFw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@commitlint/format": "^19.0.3", "@commitlint/format": "^19.0.3",
"@commitlint/lint": "^19.1.0", "@commitlint/lint": "^19.1.0",
"@commitlint/load": "^19.1.0", "@commitlint/load": "^19.2.0",
"@commitlint/read": "^19.0.3", "@commitlint/read": "^19.2.0",
"@commitlint/types": "^19.0.3", "@commitlint/types": "^19.0.3",
"execa": "^8.0.1", "execa": "^8.0.1",
"yargs": "^17.0.0" "yargs": "^17.0.0"
@ -5292,17 +5291,6 @@
"react": "^16.8 || ^17.0 || ^18.0" "react": "^16.8 || ^17.0 || ^18.0"
} }
}, },
"node_modules/@react-native-async-storage/async-storage": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz",
"integrity": "sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.60 <1.0"
}
},
"node_modules/@react-native-community/cli": { "node_modules/@react-native-community/cli": {
"version": "12.3.6", "version": "12.3.6",
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz",
@ -11525,9 +11513,9 @@
} }
}, },
"node_modules/eslint-plugin-react": { "node_modules/eslint-plugin-react": {
"version": "7.34.0", "version": "7.34.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz",
"integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"array-includes": "^3.1.7", "array-includes": "^3.1.7",
@ -13997,11 +13985,11 @@
} }
}, },
"node_modules/is-plain-obj": { "node_modules/is-plain-obj": {
"version": "2.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
"engines": { "engines": {
"node": ">=8" "node": ">=0.10.0"
} }
}, },
"node_modules/is-plain-object": { "node_modules/is-plain-object": {
@ -17761,17 +17749,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/merge-options": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"dependencies": {
"is-plain-obj": "^2.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -21261,14 +21238,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/sort-keys/node_modules/is-plain-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.7.4", "version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",

View File

@ -19,7 +19,6 @@
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "14.0.0", "@expo/vector-icons": "14.0.0",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-navigation/native": "6.1.16", "@react-navigation/native": "6.1.16",
"@supabase/supabase-js": "2.39.8", "@supabase/supabase-js": "2.39.8",
"expo": "50.0.13", "expo": "50.0.13",
@ -43,7 +42,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.24.0", "@babel/core": "7.24.0",
"@commitlint/cli": "19.1.0", "@commitlint/cli": "19.2.0",
"@commitlint/config-conventional": "19.1.0", "@commitlint/config-conventional": "19.1.0",
"@testing-library/react-native": "12.4.3", "@testing-library/react-native": "12.4.3",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
@ -60,7 +59,7 @@
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.34.0", "eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-react-native": "4.1.0", "eslint-plugin-react-native": "4.1.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "51.0.1",

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View File

@ -1,9 +1,9 @@
import { HabitsTracker } from "@/data/domain/entities/HabitsTracker" import { HabitsTracker } from "@/domain/entities/HabitsTracker"
import { Presenter } from "./_Presenter" import { Presenter } from "./_Presenter"
import type { import type {
RetrieveHabitsTrackerUseCase, RetrieveHabitsTrackerUseCase,
RetrieveHabitsTrackerUseCaseOptions, RetrieveHabitsTrackerUseCaseOptions,
} from "@/data/domain/use-cases/RetrieveHabitsTracker" } from "@/domain/use-cases/RetrieveHabitsTracker"
export interface HabitsTrackerPresenterState { export interface HabitsTrackerPresenterState {
habitsTracker: HabitsTracker habitsTracker: HabitsTracker

View File

@ -1,6 +1,6 @@
import renderer from "react-test-renderer" import renderer from "react-test-renderer"
import { ButtonCustom } from "@/components/ButtonCustom" import { ButtonCustom } from "@/presentation/react/components/ButtonCustom"
describe("<ButtonCustom />", () => { describe("<ButtonCustom />", () => {
it("renders correctly", () => { it("renders correctly", () => {

View File

@ -1,6 +1,6 @@
import renderer from "react-test-renderer" import renderer from "react-test-renderer"
import { ExternalLink } from "@/components/ExternalLink" import { ExternalLink } from "@/presentation/react/components/ExternalLink"
describe("<ExternalLink />", () => { describe("<ExternalLink />", () => {
it("renders correctly", () => { it("renders correctly", () => {

View File

@ -1,6 +1,6 @@
import renderer from "react-test-renderer" import renderer from "react-test-renderer"
import { MonoText } from "@/components/MonoText" import { MonoText } from "@/presentation/react/components/MonoText"
describe("<MonoText />", () => { describe("<MonoText />", () => {
it("renders correctly", () => { it("renders correctly", () => {

View File

@ -3,12 +3,11 @@ import { createContext, useContext, useEffect } from "react"
import type { import type {
HabitsTrackerPresenter, HabitsTrackerPresenter,
HabitsTrackerPresenterState, HabitsTrackerPresenterState,
} from "@/data/infrastructure/presenters/HabitsTrackerPresenter" } from "@/presentation/presenters/HabitsTracker"
import { usePresenterState } from "@/hooks/usePresenterState" import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
import { habitsTrackerPresenter } from "@/data/infrastructure" import { habitsTrackerPresenter } from "@/infrastructure"
export interface HabitsTrackerContextValue { export interface HabitsTrackerContextValue extends HabitsTrackerPresenterState {
habitsTrackerPresenterState: HabitsTrackerPresenterState
habitsTrackerPresenter: HabitsTrackerPresenter habitsTrackerPresenter: HabitsTrackerPresenter
} }
@ -36,7 +35,10 @@ export const HabitsTrackerProvider: React.FC<HabitsTrackerProviderProps> = (
return ( return (
<HabitsTrackerContext.Provider <HabitsTrackerContext.Provider
value={{ habitsTrackerPresenterState, habitsTrackerPresenter }} value={{
...habitsTrackerPresenterState,
habitsTrackerPresenter,
}}
> >
{children} {children}
</HabitsTrackerContext.Provider> </HabitsTrackerContext.Provider>

View File

@ -1,6 +1,6 @@
import { act, renderHook } from "@testing-library/react-native" import { act, renderHook } from "@testing-library/react-native"
import { useBoolean } from "@/hooks/useBoolean" import { useBoolean } from "@/presentation/react/hooks/useBoolean"
describe("hooks/useBoolean", () => { describe("hooks/useBoolean", () => {
beforeEach(() => { beforeEach(() => {

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import type { Presenter } from "@/data/infrastructure/presenters/_Presenter" import type { Presenter } from "@/presentation/presenters/_Presenter"
export const usePresenterState = <S>(presenter: Presenter<S>): S => { export const usePresenterState = <S>(presenter: Presenter<S>): S => {
const [state, setState] = useState<S>(presenter.initialState) const [state, setState] = useState<S>(presenter.initialState)

View File

@ -1,5 +0,0 @@
import "@testing-library/react-native/extend-expect"
jest.mock("@react-native-async-storage/async-storage", () => {
return require("@react-native-async-storage/async-storage/jest/async-storage-mock")
})