1
1
mirror of https://github.com/theoludwig/p61-project.git synced 2024-07-17 07:00:12 +02:00

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
- [Sujet du projet](./docs/SUJET.md)
- [Cahier des charges](./docs/CAHIER-DES-CHARGES.md)
- [Sujet](./docs/SUJET.md) + [Cahier des charges](./docs/CAHIER-DES-CHARGES.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)
#### 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.
- [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.
<!-- - [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.
<!--
- [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

View File

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

View File

@ -1,16 +1,14 @@
import { StyleSheet, Text, View } from "react-native"
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 { habitsTrackerPresenterState } = useHabitsTracker()
const { habitsTracker } = habitsTrackerPresenterState
const { habitProgressHistories } = habitsTracker
const { habitsTracker } = useHabitsTracker()
return (
<SafeAreaView style={styles.container}>
{habitProgressHistories.map((progressHistory) => {
{habitsTracker.habitsHistory.map((progressHistory) => {
const { habit } = progressHistory
return (

View File

@ -8,10 +8,10 @@ import {
} from "react-native-paper"
import { StatusBar } from "expo-status-bar"
import CanterburyFont from "../assets/fonts/Canterbury.ttf"
import GeoramFont from "../assets/fonts/Georama-Black.ttf"
import SpaceMonoFont from "../assets/fonts/SpaceMono-Regular.ttf"
import { HabitsTrackerProvider } from "@/contexts/HabitsTracker"
import CanterburyFont from "../presentation/assets/fonts/Canterbury.ttf"
import GeoramFont from "../presentation/assets/fonts/Georama-Black.ttf"
import SpaceMonoFont from "../presentation/assets/fonts/SpaceMono-Regular.ttf"
import { HabitsTrackerProvider } from "@/presentation/react/contexts/HabitsTracker"
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
![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:
- `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.
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
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:
- Ne concerne qu'un seul sujet (une fonctionnalité, une correction de bug, etc.)
- Doit avoir un message clair et concis
- Ne doit pas rendre de dépôt "incohérent" (bloque la CI du projet)
- Ne concerne qu'un seul sujet (une fonctionnalité, une correction de bug, etc.).
- Doit avoir un message clair et concis.
- 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 type { User } from "../entities/User"
import type { GetHabitProgressesRepository } from "../repositories/GetHabitProgresses"
import type { GetHabitProgressHistoryRepository } from "../repositories/GetHabitProgressHistory"
import type { GetHabitsByUserIdRepository } from "../repositories/GetHabitsByUserId"
export interface RetrieveHabitsTrackerUseCaseDependencyOptions {
getHabitsByUserIdRepository: GetHabitsByUserIdRepository
getHabitProgressesRepository: GetHabitProgressesRepository
getHabitProgressHistoryRepository: GetHabitProgressHistoryRepository
}
export interface RetrieveHabitsTrackerUseCaseOptions {
@ -17,11 +17,12 @@ export class RetrieveHabitsTrackerUseCase
implements RetrieveHabitsTrackerUseCaseDependencyOptions
{
public getHabitsByUserIdRepository: GetHabitsByUserIdRepository
public getHabitProgressesRepository: GetHabitProgressesRepository
public getHabitProgressHistoryRepository: GetHabitProgressHistoryRepository
public constructor(options: RetrieveHabitsTrackerUseCaseDependencyOptions) {
this.getHabitsByUserIdRepository = options.getHabitsByUserIdRepository
this.getHabitProgressesRepository = options.getHabitProgressesRepository
this.getHabitProgressHistoryRepository =
options.getHabitProgressHistoryRepository
}
public async execute(
@ -31,15 +32,16 @@ export class RetrieveHabitsTrackerUseCase
const habits = await this.getHabitsByUserIdRepository.execute({ userId })
const habitProgressHistories = await Promise.all(
habits.map(async (habit) => {
const habitProgresses = await this.getHabitProgressesRepository.execute(
{
const progressHistory =
await this.getHabitProgressHistoryRepository.execute({
habit,
},
)
return new HabitProgressHistory({ habit, habitProgresses })
})
return new HabitHistory({ habit, progressHistory })
}),
)
const habitsTracker = new HabitsTracker({ habitProgressHistories })
const habitsTracker = new HabitsTracker({
habitsHistory: habitProgressHistories,
})
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,19 +1,16 @@
// 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 { HabitsTrackerPresenter } from "./presenters/HabitsTrackerPresenter"
import { GetHabitProgressesSupabaseRepository } from "./repositories/supabase/lib/GetHabitProgresses"
import { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
import { GetHabitProgressHistorySupabaseRepository } from "./repositories/supabase/lib/GetHabitProgressHistory"
import { GetHabitsByUserIdSupabaseRepository } from "./repositories/supabase/lib/GetHabitsByUserId"
import { supabaseClient } from "./repositories/supabase/supabase"
/**
* Repositories
*/
const getHabitProgressesRepository = new GetHabitProgressesSupabaseRepository({
supabaseClient,
})
const getHabitProgressesRepository =
new GetHabitProgressHistorySupabaseRepository({
supabaseClient,
})
const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
supabaseClient,
})
@ -22,7 +19,7 @@ const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
* Use Cases
*/
const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
getHabitProgressesRepository,
getHabitProgressHistoryRepository: getHabitProgressesRepository,
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 { HabitProgress } from "@/data/domain/entities/HabitProgress"
import type { GoalProgress } from "@/data/domain/entities/Goal"
import { HabitProgress } from "@/domain/entities/HabitProgress"
import type { GoalProgress } from "@/domain/entities/Goal"
import {
GoalBooleanProgress,
GoalNumericProgress,
} from "@/data/domain/entities/Goal"
} from "@/domain/entities/Goal"
export class GetHabitProgressesSupabaseRepository
export class GetHabitProgressHistorySupabaseRepository
extends SupabaseRepository
implements GetHabitProgressesRepository
implements GetHabitProgressHistoryRepository
{
execute: GetHabitProgressesRepository["execute"] = async (options) => {
execute: GetHabitProgressHistoryRepository["execute"] = async (options) => {
const { habit } = options
const { data, error } = await this.supabaseClient
.from("habits_progresses")
@ -20,7 +20,7 @@ export class GetHabitProgressesSupabaseRepository
if (error != null) {
throw new Error(error.message)
}
const habitProgresses = data.map((item) => {
const habitProgressHistory = data.map((item) => {
let goalProgress: GoalProgress | null = null
if (habit.goal.isNumeric()) {
goalProgress = new GoalNumericProgress({
@ -44,6 +44,6 @@ export class GetHabitProgressesSupabaseRepository
})
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 { Habit } from "@/data/domain/entities/Habit"
import type { Goal } from "@/data/domain/entities/Goal"
import { GoalBoolean, GoalNumeric } from "@/data/domain/entities/Goal"
import { Habit } from "@/domain/entities/Habit"
import type { Goal } from "@/domain/entities/Goal"
import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
export class GetHabitsByUserIdSupabaseRepository
extends SupabaseRepository

View File

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

59
package-lock.json generated
View File

@ -10,7 +10,6 @@
"hasInstallScript": true,
"dependencies": {
"@expo/vector-icons": "14.0.0",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-navigation/native": "6.1.16",
"@supabase/supabase-js": "2.39.8",
"expo": "50.0.13",
@ -34,7 +33,7 @@
},
"devDependencies": {
"@babel/core": "7.24.0",
"@commitlint/cli": "19.1.0",
"@commitlint/cli": "19.2.0",
"@commitlint/config-conventional": "19.1.0",
"@testing-library/react-native": "12.4.3",
"@total-typescript/ts-reset": "0.5.1",
@ -51,7 +50,7 @@
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"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-native": "4.1.0",
"eslint-plugin-unicorn": "51.0.1",
@ -2122,15 +2121,15 @@
}
},
"node_modules/@commitlint/cli": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.1.0.tgz",
"integrity": "sha512-SYGm8HGbVzrlSYeB6oo6pG1Ec6bOMJcDsXgNGa4vgZQsPj6nJkcbTWlIRmtmIk0tHi0d5sCljGuQ+g/0NCPv7w==",
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.2.0.tgz",
"integrity": "sha512-8XnQDMyQR+1/ldbmIyhonvnDS2enEw48Wompo/967fsEvy9Vj5/JbDutzmSBKxANWDVeEbR9QQm0yHpw6ArrFw==",
"dev": true,
"dependencies": {
"@commitlint/format": "^19.0.3",
"@commitlint/lint": "^19.1.0",
"@commitlint/load": "^19.1.0",
"@commitlint/read": "^19.0.3",
"@commitlint/load": "^19.2.0",
"@commitlint/read": "^19.2.0",
"@commitlint/types": "^19.0.3",
"execa": "^8.0.1",
"yargs": "^17.0.0"
@ -5292,17 +5291,6 @@
"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": {
"version": "12.3.6",
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz",
@ -11525,9 +11513,9 @@
}
},
"node_modules/eslint-plugin-react": {
"version": "7.34.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz",
"integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==",
"version": "7.34.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz",
"integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.7",
@ -13997,11 +13985,11 @@
}
},
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"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": ">=8"
"node": ">=0.10.0"
}
},
"node_modules/is-plain-object": {
@ -17761,17 +17749,6 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -21261,14 +21238,6 @@
"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": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",

View File

@ -19,7 +19,6 @@
},
"dependencies": {
"@expo/vector-icons": "14.0.0",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-navigation/native": "6.1.16",
"@supabase/supabase-js": "2.39.8",
"expo": "50.0.13",
@ -43,7 +42,7 @@
},
"devDependencies": {
"@babel/core": "7.24.0",
"@commitlint/cli": "19.1.0",
"@commitlint/cli": "19.2.0",
"@commitlint/config-conventional": "19.1.0",
"@testing-library/react-native": "12.4.3",
"@total-typescript/ts-reset": "0.5.1",
@ -60,7 +59,7 @@
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"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-native": "4.1.0",
"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 type {
RetrieveHabitsTrackerUseCase,
RetrieveHabitsTrackerUseCaseOptions,
} from "@/data/domain/use-cases/RetrieveHabitsTracker"
} from "@/domain/use-cases/RetrieveHabitsTracker"
export interface HabitsTrackerPresenterState {
habitsTracker: HabitsTracker

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
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 => {
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")
})