Compare commits

..

No commits in common. 'master' and 'part2' have entirely different histories.

@ -1,17 +0,0 @@
kind: pipeline
type: docker
name: JokesApp
steps:
- name: sonar-analyses
image: hub.codefirst.iut.uca.fr/camille.petitalot/drone-sonarplugin-reactnative:latest
commands:
- cd src
- npm install
- sonar-scanner -Dsonar.projectKey=JokesAppReact_A-PE -Dsonar.sources=. -Dsonar.host.url=$${PLUGIN_SONAR_HOST}
-Dsonar.login=$${PLUGIN_SONAR_TOKEN}
secrets: [ SECRET_SONAR_LOGIN ]
settings:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN

@ -64,11 +64,11 @@ Le projet de tp utilise un modèle de flux de travail Git pour organiser le dév
* [X] Partie 4 - Components * [X] Partie 4 - Components
* [X] Partie 5 - FlatList * [X] Partie 5 - FlatList
* [X] Partie 6 - Safe Area * [X] Partie 6 - Safe Area
* [X] Partie 7 - Navigation * [ ] Partie 7 - Navigation
* [X] Partie 8 - Hooks * [ ] Partie 8 - Hooks
* [X] Partie 9 - Redux Store * [ ] Partie 9 - Redux Store
* [X] Partie 10 - Async Storage * [ ] Partie 10 - Async Storage
* [X] Partie 11 - Theming * [ ] Partie 11 - Theming
* [ ] Partie 12 - Unit testing * [ ] Partie 12 - Unit testing
* [ ] Partie 13 - Resources * [ ] Partie 13 - Resources

@ -1,8 +0,0 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.

@ -1,3 +0,0 @@
{
"devices": []
}

@ -1 +0,0 @@
module.exports = $$require_external('node:assert');

@ -1 +0,0 @@
module.exports = $$require_external('node:async_hooks');

@ -1 +0,0 @@
module.exports = $$require_external('node:buffer');

@ -1 +0,0 @@
module.exports = $$require_external('node:child_process');

@ -1 +0,0 @@
module.exports = $$require_external('node:cluster');

@ -1 +0,0 @@
module.exports = $$require_external('node:console');

@ -1 +0,0 @@
module.exports = $$require_external('node:constants');

@ -1 +0,0 @@
module.exports = $$require_external('node:crypto');

@ -1 +0,0 @@
module.exports = $$require_external('node:dgram');

@ -1 +0,0 @@
module.exports = $$require_external('node:diagnostics_channel');

@ -1 +0,0 @@
module.exports = $$require_external('node:dns');

@ -1 +0,0 @@
module.exports = $$require_external('node:domain');

@ -1 +0,0 @@
module.exports = $$require_external('node:events');

@ -1 +0,0 @@
module.exports = $$require_external('node:fs');

@ -1 +0,0 @@
module.exports = $$require_external('node:fs/promises');

@ -1 +0,0 @@
module.exports = $$require_external('node:http');

@ -1 +0,0 @@
module.exports = $$require_external('node:http2');

@ -1 +0,0 @@
module.exports = $$require_external('node:https');

@ -1 +0,0 @@
module.exports = $$require_external('node:inspector');

@ -1 +0,0 @@
module.exports = $$require_external('node:module');

@ -1 +0,0 @@
module.exports = $$require_external('node:net');

@ -1 +0,0 @@
module.exports = $$require_external('node:os');

@ -1 +0,0 @@
module.exports = $$require_external('node:path');

@ -1 +0,0 @@
module.exports = $$require_external('node:perf_hooks');

@ -1 +0,0 @@
module.exports = $$require_external('node:process');

@ -1 +0,0 @@
module.exports = $$require_external('node:punycode');

@ -1 +0,0 @@
module.exports = $$require_external('node:querystring');

@ -1 +0,0 @@
module.exports = $$require_external('node:readline');

@ -1 +0,0 @@
module.exports = $$require_external('node:repl');

@ -1 +0,0 @@
module.exports = $$require_external('node:stream');

@ -1 +0,0 @@
module.exports = $$require_external('node:string_decoder');

@ -1 +0,0 @@
module.exports = $$require_external('node:timers');

@ -1 +0,0 @@
module.exports = $$require_external('node:tls');

@ -1 +0,0 @@
module.exports = $$require_external('node:trace_events');

@ -1 +0,0 @@
module.exports = $$require_external('node:tty');

@ -1 +0,0 @@
module.exports = $$require_external('node:url');

@ -1 +0,0 @@
module.exports = $$require_external('node:util');

@ -1 +0,0 @@
module.exports = $$require_external('node:v8');

@ -1 +0,0 @@
module.exports = $$require_external('node:vm');

@ -1 +0,0 @@
module.exports = $$require_external('node:wasi');

@ -1 +0,0 @@
module.exports = $$require_external('node:worker_threads');

@ -1 +0,0 @@
module.exports = $$require_external('node:zlib');

@ -1 +0,0 @@
global.$$require_external = typeof window === "undefined" ? require : () => null;

@ -1 +0,0 @@
global.$$require_external = (moduleId) => {throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);}

@ -1,25 +0,0 @@
import { SafeAreaView } from 'react-native'
import {StyleSheet} from 'react-native';
import NavigationBar from "./navigation/NavigationBar";
import {indigoColor} from "./assets/Theme";
import store from "./redux/store";
import React from "react";
import {Provider} from "react-redux";
export default function App() {
return (
<Provider store={store}>
<SafeAreaView style={{flex: 0, backgroundColor: 'darksalmon'}}/>
<SafeAreaView style={styles.container}>
<NavigationBar />
</SafeAreaView>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: indigoColor
},
});

@ -1,32 +0,0 @@
import {Theme} from "@react-navigation/native";
export const indigoColor = "rgba(14, 14, 44, 1)";
export const purpleColor = "rgba(74, 74, 104, 1)";
export const darksalmonColor = "rgba(233, 150, 122, 1)";
export const greyColor = "rgba(140, 140, 161, 1)";
export const whiteColor = "rgba(239, 239, 253, 1)";
export const LightTheme: Theme = {
dark: false,
colors: {
primary: darksalmonColor,
background: whiteColor,
card: greyColor,
text: "black",
border: whiteColor,
notification: 'rgb(255, 59, 48)',
},
};
export const DarkTheme: Theme = {
dark: true,
colors: {
primary: darksalmonColor,
background: purpleColor,
card: indigoColor,
text: whiteColor,
border: greyColor,
notification: 'rgb(255, 69, 58)',
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@ -1,27 +0,0 @@
import {StyleSheet, Text, View} from 'react-native';
import {greyColor} from "../assets/Theme";
import {Category} from "../model/Category";
type CategItemProps = {
category: Category;
};
export default function Categ(prop: CategItemProps) {
return (
<View style={styles.bottomContainer}>
<Text style={{color:'white'}}>{prop.category.name}</Text>
</View>
);
}
const styles = StyleSheet.create({
bottomContainer: {
backgroundColor: greyColor,
paddingVertical: 5,
paddingHorizontal: 10,
margin: 10,
borderRadius: 20,
width : 120,
alignItems : 'center'
}
});

@ -1,26 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import Categ from './Categ';
import { Text, View } from 'react-native';
import {describe, expect, it} from "@jest/globals";
describe('Categ Component', () => {
it('renders correctly', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
expect(wrapper).toMatchSnapshot();
});
it('displays category name', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
expect(wrapper.find(Text).prop('children')).toEqual(category.name);
});
it('applies correct styles', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
const containerStyle = wrapper.find(View).prop('style');
expect(containerStyle).toEqual(expect.objectContaining(styles.bottomContainer));
});
});

@ -1,21 +0,0 @@
import {FlatList} from 'react-native';
import Categ from "./Categ";
import {Category} from "../model/Category";
type CategListItemProps = {
categories: Category[];
};
export default function Categs(props: CategListItemProps) {
return (
<FlatList showsHorizontalScrollIndicator={false} horizontal={true}
data={props.categories.sort((a, b) => b.number - a.number)}
keyExtractor={(item) => item.name}
renderItem={
({ item }: { item: Category }) => (
<Categ category={item}/>
)
}
/>
);
}

@ -1,36 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import Categs from './Categs';
import { FlatList } from 'react-native';
import Categ from './Categ';
import { Category } from '../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Categs Component', () => {
const categories: Category[] = [
new Category('Category 1', 10),
new Category('Category 2', 5),
new Category('Category 3', 15),
];
it('renders correctly', () => {
const wrapper = shallow(<Categs categories={categories} />);
expect(wrapper).toMatchSnapshot();
});
it('renders FlatList with correct props', () => {
const wrapper = shallow(<Categs categories={categories} />);
const flatList = wrapper.find(FlatList);
expect(flatList.exists()).toBeTruthy();
expect(flatList.prop('showsHorizontalScrollIndicator')).toBe(false);
expect(flatList.prop('horizontal')).toBe(true);
expect(flatList.prop('data')).toEqual(categories.sort((a, b) => b.number - a.number));
expect(flatList.prop('keyExtractor')).toBeInstanceOf(Function);
expect(flatList.prop('renderItem')).toBeInstanceOf(Function);
});
it('renders correct number of Categ components', () => {
const wrapper = shallow(<Categs categories={categories} />);
expect(wrapper.find(Categ)).toHaveLength(categories.length);
});
});

@ -1,178 +0,0 @@
import {StyleSheet, Text, View, Image, Button, TouchableOpacity} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {darksalmonColor, whiteColor, greyColor, indigoColor, purpleColor} from "../assets/Theme";
import React, {useEffect, useState} from "react";
import {CustomJoke} from "../model/CustomJoke";
import {useDispatch, useSelector} from "react-redux";
import {getCustomJokeById, getJokeById} from "../redux/thunk/GetByThunk";
import {getCustomJokesList} from "../redux/thunk/GetThunk";
import {deleteCustomJoke} from "../redux/thunk/DeleteThunk";
import {useNavigation} from "@react-navigation/native";
import {AppDispatch} from "../redux/store";
type JokeItemProps = {
joke: SampleJoke | CustomJoke;
};
const eye = require("../assets/eye_icon.png")
const hideEye = require("../assets/eye_off_icon.png")
const heart = require("../assets/favorite_icon.png")
const bin = require("../assets/bin.png")
const heartPlain = require("../assets/plain_favorite_icon.png")
export default function JokeDetail(props: JokeItemProps) {
const [showDescription, setShowDescription] = useState(false);
const [showFavorite, setShowFavorite] = useState(false);
const isCustom = props.joke instanceof CustomJoke;
const dispatch = useDispatch<AppDispatch>();
const navigation = useNavigation();
const toggleDescription = () => {
setShowDescription(!showDescription);
};
const toggleFavorite = () => {
setShowFavorite(!showFavorite);
};
const deleteCustomJokes = async () => {
await dispatch(deleteCustomJoke(props.joke.id));
await dispatch(getCustomJokesList());
navigation.goBack();
};
return(
<View style={styles.container}>
<Image source={{ uri: props.joke.image }} style={styles.image} />
<View style={styles.bottomContainer}>
<Text style={{color: indigoColor}}>{props.joke.type}</Text>
</View>
<Text style={styles.text}>Résumé de la blague</Text>
<View style={styles.row}>
{isCustom && (<TouchableOpacity style={styles.favContainer} onPress={ deleteCustomJokes }>
<View>
<Image
source={bin}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>)
}
<TouchableOpacity style={styles.favContainer} onPress={toggleFavorite}>
<View>
<Image
source={showFavorite ? heartPlain : heart}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>
<TouchableOpacity style={styles.Button} onPress={toggleDescription}>
<View style={styles.chuteContainer}>
<Image
source={showDescription ? hideEye : eye}
style={styles.imageButton}
/>
<Text style={styles.TextButton} >LA CHUTE</Text>
</View>
</TouchableOpacity>
</View>
{showDescription && <Text style={styles.text}>{props.joke.description()}</Text>}
</View>
);
}
const styles = StyleSheet.create({
image : {
margin : 5,
width: '90%',
height:200,
top : 5,
alignSelf : "center",
backgroundColor: "white",
borderRadius: 5,
},
Button:{
borderRadius : 5,
backgroundColor : darksalmonColor,
height:50,
width : 160,
flexDirection : "row"
},
imageButton : {
margin : 10,
width: 50,
height:50,
top : 5,
alignSelf : "center",
backgroundColor: "none",
tintColor : whiteColor
},
favContainer : {
margin : 20,
borderWidth : 3,
borderRadius : 15,
borderColor : whiteColor,
borderStyle : "solid"
},
TextButton : {
margin: 10,
textAlign : "center",
fontWeight: "700",
color : whiteColor,
},
chuteContainer :{
display : "flex",
flex : 1,
flexDirection: "row",
alignItems : "center"
},
container: {
marginHorizontal: "5%",
display: "flex",
marginBottom:7,
marginTop:7,
paddingVertical: 10,
backgroundColor: indigoColor,
justifyContent: 'space-between',
borderRadius: 20,
height: "auto",
borderColor: whiteColor,
borderStyle: "solid",
borderWidth: 1
},
row: {
display: "flex",
flexDirection:"row",
alignSelf: "flex-end",
},
color: {
flex: 0,
backgroundColor: darksalmonColor,
height: 150,
width:15,
},
columnContainer: {
flexDirection: "column",
marginLeft: 20,
marginRight: 20,
width: '60%',
flex: 2,
justifyContent: 'space-between',
},
text: {
color:greyColor,
paddingBottom: 7,
paddingTop: 7,
marginLeft: 19,
fontSize: 16,
},
bottomContainer: {
backgroundColor: whiteColor,
paddingVertical: 5,
paddingHorizontal: 10,
margin: 10,
marginLeft: 19,
marginTop: 20,
borderRadius: 20,
width : 150,
alignItems : 'center'
}
})

@ -1,54 +0,0 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import JokeDetail from '../path/to/JokeDetail';
import {describe} from "@jest/globals"; // Remplacez le chemin par le bon chemin
describe('JokeDetail Component', () => {
const sampleJoke = {
id: '1',
type: 'Sample',
image: 'sample-image-url',
description: () => 'Sample Joke Description'
};
const customJoke = {
id: '2',
type: 'Custom',
image: 'custom-image-url',
description: () => 'Custom Joke Description'
};
test('renders joke image', () => {
const { getByTestId } = render(<JokeDetail joke={sampleJoke} />);
const jokeImage = getByTestId('joke-image');
expect(jokeImage).toBeTruthy();
});
test('renders joke description when toggled', () => {
const { getByText } = render(<JokeDetail joke={sampleJoke} />);
const toggleButton = getByText('LA CHUTE');
fireEvent.press(toggleButton);
const jokeDescription = getByText('Sample Joke Description');
expect(jokeDescription).toBeTruthy();
});
test('toggles favorite icon when pressed', () => {
const { getByTestId } = render(<JokeDetail joke={sampleJoke} />);
const favoriteButton = getByTestId('favorite-button');
fireEvent.press(favoriteButton);
expect(favoriteButton.props.source).toEqual(require('../assets/plain_favorite_icon.png'));
});
test('calls deleteCustomJokes and updates list when delete button is pressed for custom joke', () => {
const deleteMock = jest.fn();
const dispatchMock = jest.fn();
jest.mock('../redux/thunk/DeleteThunk', () => ({ deleteCustomJoke: deleteMock }));
jest.mock('react-redux', () => ({ useDispatch: () => dispatchMock }));
const { getByTestId } = render(<JokeDetail joke={customJoke} />);
const deleteButton = getByTestId('delete-button');
fireEvent.press(deleteButton);
expect(deleteMock).toHaveBeenCalledWith(customJoke.id);
expect(dispatchMock).toHaveBeenCalledTimes(2); // Assuming one dispatch for delete and one for update list
});
});

@ -1,67 +0,0 @@
import {StyleSheet, Text, View, Image} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import {darksalmonColor, whiteColor, indigoColor} from "../assets/Theme";
type JokeListItemProps = {
joke: (CustomJoke | SampleJoke);
};
export default function JokeHomeSquare(prop: JokeListItemProps) {
return (
<View style={styles.container}>
<View style={styles.topBackgroundColor}>
<View style={{width: 200, height: 40, backgroundColor: darksalmonColor}}/>
</View>
<View style={styles.bottomBackgroundColor}>
<View style={{width: 200, height: 120, backgroundColor: indigoColor}}/>
</View>
<Image source={{ uri: prop.joke.image }} style={styles.image} />
<Text style={[styles.text, styles.textTitle]}>Résumé de la blague</Text>
<Text style={[styles.text, styles.textSimple]}>{prop.joke.description()}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 10,
marginLeft: 20,
position: "relative",
},
topBackgroundColor: {
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
overflow: 'hidden',
},
bottomBackgroundColor: {
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
overflow: 'hidden',
},
image: {
width: 120,
height: 75,
position: "absolute",
top: 5,
left: "50%",
marginLeft: -60
},
text: {
position: 'absolute',
textAlign: "center",
color:whiteColor,
paddingBottom: 7,
paddingTop: 7,
width: "100%"
},
textTitle: {
fontSize: 17,
fontWeight: "bold",
top: 90
},
textSimple: {
fontSize: 15,
top: 115
}
});

@ -1,35 +0,0 @@
import {FlatList, Image, StyleSheet, Text, TouchableHighlight, TouchableOpacity, View} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import JokeItem from "./JokeItem";
import React, {useState} from "react";
import {useNavigation} from "@react-navigation/native";
import {darksalmonColor, greyColor, indigoColor, whiteColor} from "../assets/Theme";
type JokeListItemProps = {
jokes: (CustomJoke | SampleJoke)[];
};
export default function JokeItems(props: JokeListItemProps) {
const navigation = useNavigation()
return (
<FlatList
data={props.jokes}
keyExtractor={(item) => item.id.toString()}
renderItem={
({ item }: { item: CustomJoke | SampleJoke }) => (
// @ts-ignore
<TouchableHighlight onPress={() => navigation.navigate("JokeDetails", {"idJoke": item.id})}>
<JokeItem joke={item}/>
</TouchableHighlight>
)
}
/>
);
}
const styles = StyleSheet.create({
})

@ -1,22 +0,0 @@
import {FlatList} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import JokeHomeSquare from "./JokeHomeSquare";
type JokeListItemProps = {
jokes: (CustomJoke | SampleJoke)[];
};
export default function JokesHomeSquare(props: JokeListItemProps) {
return (
<FlatList showsHorizontalScrollIndicator={false} horizontal={true}
data={props.jokes}
renderItem={
({ item }: { item: CustomJoke | SampleJoke }) => (
<JokeHomeSquare joke={item}/>
)
}
keyExtractor={(item) => item.id.toString()}
/>
);
}

@ -1,13 +0,0 @@
/**
* @file CustomJokeStub.ts
* @brief Exemple d'utilisation de la classe JokeFactory pour créer des blagues personnalisées.
*/
import { JokeFactory } from '../../model/JokeFactory';
/**
* @brief Stub de blagues personnalisées créées à l'aide de la classe JokeFactory.
* @constant
* @type {CustomJoke[]}
*/
export const customJokeStub = JokeFactory.createCustomJokes('[{"id":"premier", "type":"custom", "setup":"one", "punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"},{"id":"deuxieme", "type":"custom", "setup":"two","punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"},{"id":"troisieme", "type":"Default", "setup":"three","punchline":"y\'en toujours a pas ;)", "image":"https://placekitten.com/200/300"},{"id":"quatrieme", "type":"custom bro", "setup":"four","punchline":"y\'en toujours toujours ap", "image":"https://placekitten.com/200/300"}]');

@ -1,55 +0,0 @@
/**
* @file Category.ts
* @brief Définition de la classe Catégory.
*/
/**
* @class
* @brief Représente une catégorie nom.
*/
export class Category {
private _name: string;
private _number: number;
/**
* @brief Constructeur de la classe Category.
* @param {string} name - Le nom de la catégorie.
* @param {number} number - Le nombre de la catégorie.
*/
constructor(name: string, number: number) {
this._name = name;
this._number = number;
}
/**
* @brief Obtient le nom de la catégorie.
* @return {string} Le nom de la catégorie.
*/
get name(): string {
return this._name;
}
/**
* @brief Obtient le nombre de la catégorie.
* @return {number} Le nombre de la catégorie.
*/
get number(): number {
return this._number;
}
/**
* @brief Modifie le nom de la catégorie.
* @param {string} name - Le nom de la categorie.
*/
set name(name: string) {
this._name = name;
}
/**
* @brief Modifie le nombre de la catégorie.
* @param {number} number - Le nombre de la catégorie.
*/
set number(number: number) {
this._number = number;
}
}

@ -1,25 +0,0 @@
import { Category } from './Category';
import {describe, expect, it} from "@jest/globals";
describe('Category Class Constructor', () => {
it('should create a new Category object', () => {
const category = new Category('name', 5);
expect(category).toBeDefined();
expect(category.name).toBe('name');
expect(category.number).toBe(5);
});
});
describe('Category Class Accessors', () => {
it('should set and get the name correctly', () => {
const category = new Category('name', 5);
category.name = 'newName';
expect(category.name).toBe('newName');
});
it('should set and get the number correctly', () => {
const category = new Category('name', 5);
category.number = 10;
expect(category.number).toBe(10);
});
});

@ -1,35 +0,0 @@
import { CustomJoke } from './CustomJoke';
import {describe, expect, test} from "@jest/globals"; // Remplacez le chemin par le bon chemin
describe('CustomJoke Class', () => {
const id = '1';
const type = 'Custom';
const setup = 'Why did the developer go broke?';
const image = 'custom-image-url';
const punchline = 'Because he used up all his cache';
test('creates a new instance of CustomJoke', () => {
const customJoke = new CustomJoke(id, type, setup, image, punchline);
expect(customJoke).toBeInstanceOf(CustomJoke);
expect(customJoke.id).toEqual(id);
expect(customJoke.type).toEqual(type);
expect(customJoke.setup).toEqual(setup);
expect(customJoke.punchline).toEqual(punchline);
expect(customJoke.image).toEqual(image);
});
test('updates CustomJoke properties', () => {
const newSetup = 'Why do programmers prefer dark mode?';
const newImage = 'new-custom-image-url';
const newPunchline = 'Because light attracts bugs';
const customJoke = new CustomJoke(id, type, setup, image, punchline);
customJoke.setup = newSetup;
customJoke.image = newImage;
customJoke.punchline = newPunchline;
expect(customJoke.setup).toEqual(newSetup);
expect(customJoke.image).toEqual(newImage);
expect(customJoke.punchline).toEqual(newPunchline);
});
});

@ -1,61 +0,0 @@
const { Joke } = require('./Joke');
const {expect, it, beforeEach, describe} = require("@jest/globals");
// Mock class extending the abstract Joke class
class MockJoke extends Joke {
constructor(type, setup, punchline, image) {
super(type, setup, punchline, image);
}
}
// Test the Joke class
describe('Joke Class', () => {
let joke;
beforeEach(() => {
joke = new MockJoke('type', 'setup', 'punchline', 'image');
});
// Test the constructor
it('should create a new Joke object', () => {
expect(joke).toBeDefined();
expect(joke.type).toBe('type');
expect(joke.setup).toBe('setup');
expect(joke.punchline).toBe('punchline');
expect(joke.image).toBe('image');
});
// Test the summary() method
it('should return a summary of the joke', () => {
expect(joke.summary()).toBe('punchline');
});
// Test the description() method
it('should return a textual description of the joke', () => {
expect(joke.description()).toBe('type, punchline');
});
// Test setting and getting the type
it('should set and get the type correctly', () => {
joke.type = 'newType';
expect(joke.type).toBe('newType');
});
// Test setting and getting the setup
it('should set and get the setup correctly', () => {
joke.setup = 'newSetup';
expect(joke.setup).toBe('newSetup');
});
// Test setting and getting the punchline
it('should set and get the punchline correctly', () => {
joke.punchline = 'newPunchline';
expect(joke.punchline).toBe('newPunchline');
});
// Test setting and getting the image
it('should set and get the image correctly', () => {
joke.image = 'newImage';
expect(joke.image).toBe('newImage');
});
});

@ -1,126 +0,0 @@
import {DarkTheme, DefaultTheme, NavigationContainer, Theme} from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import {Image, View} from 'react-native';
import HomeScreen from "../screens/HomeScreen";
import Favorites from "../screens/Favorites";
import Add from "../screens/AddScreen";
import Settings from "../screens/Settings";
import {darksalmonColor, greyColor, indigoColor} from "../assets/Theme";
import StackNavigation from "./StackNavigation";
import {useEffect, useState} from "react";
import {getTheme} from "../redux/thunk/ThemeThunk";
export default function NavigationBar() {
const BottomTabNavigator = createBottomTabNavigator();
const homeIcon = require("../assets/home_icon.png");
const listIcon = require("../assets/list_icon.png");
const addIcon = require("../assets/add_icon.png");
const favoriteIcon = require("../assets/favorite_icon.png");
const settingsIcon = require("../assets/settings_icon.png");
const [themes, setTheme] = useState<Theme>(DefaultTheme);
useEffect(() => {
const fetchTheme = async () => {
const theme = await getTheme();
setTheme(theme);
};
fetchTheme();
});
if (themes == null) {
return null;
}
return (
<NavigationContainer theme={ themes.dark === false ? DefaultTheme : DarkTheme}>
<BottomTabNavigator.Navigator initialRouteName="Accueil"
screenOptions={{
headerStyle: {
backgroundColor: indigoColor,
},
headerTitleStyle: {
color:darksalmonColor,
fontSize:24,
textAlign: "center",
paddingBottom:20,
},
headerTitleAlign: 'center',
tabBarShowLabel: false,
tabBarStyle: {
backgroundColor: indigoColor,
}
}}
>
<BottomTabNavigator.Screen name="Accueil" component={HomeScreen}
options={{
tabBarIcon: ({focused}) => (
<Image
source={homeIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
<BottomTabNavigator.Screen name="Catalogue" component={StackNavigation}
options={{
headerShown: false,
tabBarIcon: ({focused}) => (
<Image
source={listIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
<BottomTabNavigator.Screen name="Ajout d'une blague" component={Add}
options={{
tabBarIcon: ({focused}) => (
<View style={{backgroundColor: greyColor, borderRadius: 5, padding: 10}}>
<Image
source={addIcon}
style={{
width: 20, height: 20,
tintColor: focused ? darksalmonColor : "black",
}}
/>
</View>
)
}}/>
<BottomTabNavigator.Screen name="Favoris" component={Favorites}
options={{
tabBarIcon: ({focused}) => (
<Image
source={favoriteIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
<BottomTabNavigator.Screen name="Parametres" component={Settings}
options={{
tabBarIcon: ({focused}) => (
<Image
source={settingsIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
</BottomTabNavigator.Navigator>
</NavigationContainer>
)
}

@ -1,26 +0,0 @@
import {createStackNavigator} from "@react-navigation/stack";
import Catalogue from "../screens/Catalogue";
import JokeDetailsScreen from "../screens/JokeDetailsScreen";
import {darksalmonColor, indigoColor} from "../assets/Theme";
export default function StackNavigation() {
const Stack = createStackNavigator();
return (
<Stack.Navigator initialRouteName="catalogue" screenOptions={{
headerStyle: {
backgroundColor: indigoColor,
},
headerTitleStyle: {
marginTop: 10,
color:darksalmonColor,
fontSize:24,
textAlign: "center",
paddingBottom:30,
},
headerTitleAlign: 'center'
}}>
<Stack.Screen name="catalogue" component={Catalogue} />
<Stack.Screen name="JokeDetails" component={JokeDetailsScreen}/>
</Stack.Navigator>
)
}

@ -1,63 +0,0 @@
{
"name": "tp-react-native",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"ts:check": "tsc",
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest"
},
"dependencies": {
"@jest/globals": "^29.7.0",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.10",
"@react-navigation/stack": "^6.3.21",
"@reduxjs/toolkit": "^2.2.1",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^12.4.5",
"@types/react": "~18.2.45",
"enzyme": "^3.11.0",
"expo": "~50.0.3",
"expo-status-bar": "~1.11.1",
"jest": "^29.7.0",
"jest-expo": "^50.0.4",
"react": "18.2.0",
"react-native": "0.73.2",
"react-native-gesture-handler": "^2.15.0",
"react-native-safe-area-context": "^4.9.0",
"react-redux": "^9.1.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"typescript": "^5.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/fetch-mock": "^7.3.8",
"@types/redux-mock-store": "^1.0.6",
"fetch-mock": "^9.11.0",
"redux-mock-store": "^1.5.4"
},
"jest": {
"preset": "jest-expo",
"verbose": true,
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
],
"testMatch": [
"**.test.js"
],
"testEnvironment": "node",
"testEnvironmentOptions": {
"browsers": [
"chrome",
"firefox",
"safari"
]
}
},
"private": true
}

@ -1,19 +0,0 @@
import {Category} from "../../model/Category";
export enum ActionType {
FETCH_CATEGORIES = 'FETCH_CATEGORIES',
}
type actionFetch = {
type: ActionType.FETCH_CATEGORIES;
payload: Category[];
}
export type Action = actionFetch;
export const setCategoriesList = (categoriesList: Category[]) => {
return {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
}

@ -1,23 +0,0 @@
import { ActionType, Action, setCategoriesList } from './CategoryAction';
import { Category } from '../../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setCategoriesList', () => {
it('should create an action to set categories list', () => {
const categoriesList: Category[] = [
new Category('Category 1', 1),
new Category('Category 2', 2)
];
const expectedAction: Action = {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
const action = setCategoriesList(categoriesList);
expect(action).toEqual(expectedAction);
});
});
});

@ -1,53 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType {
FETCH_CUSTOM_JOKES = 'FETCH_CUSTOM_JOKES',
POST_CUSTOM_JOKE = "POST_CUSTOM_JOKE",
FETCH_JOKES_BY_ID = "FETCH_JOKES_BY_ID",
DELETE_CUSTOM_JOKE = "DELETE_CUSTOM_JOKE"
}
type actionPostFetch = {
type: ActionType.POST_CUSTOM_JOKE;
payload: CustomJoke;
}
type actionGetFetch = {
type: ActionType.FETCH_CUSTOM_JOKES;
payload: CustomJoke[];
}
type actionGetByFetch = {
type: ActionType.FETCH_JOKES_BY_ID;
payload: CustomJoke;
}
type actionDeleteFetch = {
type: ActionType.DELETE_CUSTOM_JOKE;
payload: string;
}
export type Action = actionPostFetch | actionGetFetch | actionGetByFetch | actionDeleteFetch;
export const setPostJoke = (customJoke: CustomJoke) => {
return {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke
}
}
export const setCustomJokesList = (customJokesList: CustomJoke[]) => {
return {
type: ActionType.FETCH_CUSTOM_JOKES,
payload: customJokesList
};
}
export const setCustomJokeById = (customJoke: CustomJoke) => {
return {
type: ActionType.FETCH_JOKES_BY_ID,
payload: customJoke,
};
}
export const setDeleteJoke = (jokeId: string) => {
return {
type: ActionType.DELETE_CUSTOM_JOKE,
payload: jokeId
}
}

@ -1,56 +0,0 @@
import { ActionType, Action, setPostJoke, setCustomJokesList, setCustomJokeById, setDeleteJoke } from './CustomJoke';
import { CustomJoke } from '../../model/CustomJoke';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setPostJoke', () => {
it('should create an action to post a custom joke', () => {
const customJoke: CustomJoke = new CustomJoke('1', 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const expectedAction: Action = {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke,
};
const action = setPostJoke(customJoke);
expect(action).toEqual(expectedAction);
});
});
describe('setCustomJokesList', () => {
it('should create an action to set custom jokes list', () => {
const customJokesList: CustomJoke[] = [
new CustomJoke('1', 'pun', 'Setup 1', 'https://example.com/image1.jpg', 'Punchline 1'),
new CustomJoke('2', 'pun', 'Setup 2', 'https://example.com/image2.jpg', 'Punchline 2')
];
const expectedAction: Action = {
type: ActionType.FETCH_CUSTOM_JOKES,
payload: customJokesList,
};
const action = setCustomJokesList(customJokesList);
expect(action).toEqual(expectedAction);
});
});
describe('setCustomJokeById', () => {
it('should create an action to set custom joke by ID', () => {
const customJoke: CustomJoke = new CustomJoke('1', 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const expectedAction: Action = {
type: ActionType.FETCH_JOKES_BY_ID,
payload: customJoke,
};
const action = setCustomJokeById(customJoke);
expect(action).toEqual(expectedAction);
});
});
describe('setDeleteJoke', () => {
it('should create an action to delete a custom joke', () => {
const jokeId = '1';
const expectedAction: Action = {
type: ActionType.DELETE_CUSTOM_JOKE,
payload: jokeId,
};
const action = setDeleteJoke(jokeId);
expect(action).toEqual(expectedAction);
});
});
});

@ -1,18 +0,0 @@
export enum ActionType {
SET_THEME = 'SET_THEME',
}
type actionFetch = {
type: ActionType.SET_THEME;
payload: String;
}
export type Action = actionFetch;
export const setTheme = (theme) => {
return {
type: ActionType.SET_THEME,
payload: theme,
};
}

@ -1,28 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType {
ADD_FAVORITE = 'ADD_CUSTOM_FAVORITE',
REMOVE_FAVORITE = "REMOVE_CUSTOM_FAVORITE"
}
type actionAddFetch = {
type: ActionType.ADD_FAVORITE;
payload: string;
}
type actionRemoveFetch = {
type: ActionType.REMOVE_FAVORITE;
payload: string;
}
export type Action = actionAddFetch | actionRemoveFetch;
export const addFavorite = (joke: CustomJoke | SampleJoke) => ({
type: ActionType.ADD_FAVORITE,
payload: joke,
});
export const removeFavorite = (joke: CustomJoke | SampleJoke) => ({
type: ActionType.REMOVE_FAVORITE,
payload: joke,
});

@ -1,30 +0,0 @@
import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType {
FETCH_JOKES = 'FETCH_JOKES',
FETCH_JOKES_BY_ID = 'FETCH_JOKES_BY_ID',
}
type actionFetch = {
type: ActionType.FETCH_JOKES;
payload: SampleJoke[];
}
type actionFetchById = {
type: ActionType.FETCH_JOKES_BY_ID;
payload: SampleJoke;
}
export type Action = actionFetch | actionFetchById;
export const setJokesList = (jokesList: SampleJoke[]) => {
return {
type: ActionType.FETCH_JOKES,
payload: jokesList,
};
}
export const setJokeById = (joke: SampleJoke) => {
return {
type: ActionType.FETCH_JOKES_BY_ID,
payload: joke,
};
}

@ -1,32 +0,0 @@
import { ActionType, Action, setJokesList, setJokeById } from './SampleAction';
import { SampleJoke } from '../../model/SampleJoke';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setJokesList', () => {
it('should create an action to set jokes list', () => {
const jokesList: SampleJoke[] = [
new SampleJoke(1, 'pun', 'Setup 1', 'https://example.com/image1.jpg', 'Punchline 1'),
new SampleJoke(2, 'pun', 'Setup 2', 'https://example.com/image2.jpg', 'Punchline 2')
];
const expectedAction: Action = {
type: ActionType.FETCH_JOKES,
payload: jokesList,
};
const action = setJokesList(jokesList);
expect(action).toEqual(expectedAction);
});
});
describe('setJokeById', () => {
it('should create an action to set joke by ID', () => {
const joke: SampleJoke = new SampleJoke(1, 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const expectedAction: Action = {
type: ActionType.FETCH_JOKES_BY_ID,
payload: joke,
};
const action = setJokeById(joke);
expect(action).toEqual(expectedAction);
});
});
});

@ -1,24 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
import {Action, ActionType} from "../actions/CategoryAction";
import {Category} from "../../model/Category";
interface State {
categories: Category[];
}
const initialState = {
categories: []
}
const categoryReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.FETCH_CATEGORIES:
// @ts-ignore
return {...state, categories: action.payload};
default:
return state;
}
}
export default categoryReducer;

@ -1,23 +0,0 @@
import categoryReducer from './CategoryReducer';
import { ActionType as CategoryActionType } from '../actions/CategoryAction';
import { Category } from '../../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Category Reducer', () => {
it('should handle FETCH_CATEGORIES', () => {
const categories = [
new Category('Category 1', 1),
new Category('Category 2', 2)
];
const action = {
type: CategoryActionType.FETCH_CATEGORIES,
payload: categories
};
expect(categoryReducer(undefined, action)).toEqual({
categories: categories
});
});
});

@ -1,31 +0,0 @@
import {CustomJoke} from "../../model/CustomJoke";
import {Action, ActionType} from "../actions/CustomJoke";
interface State {
customJokes: CustomJoke[];
customJoke?: CustomJoke;
jokeId: string;
}
const initialState = {
customJokes: [],
customJoke: null,
jokeId: ''
}
const customReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.POST_CUSTOM_JOKE:
return {...state, customJoke: action.payload};
case ActionType.FETCH_CUSTOM_JOKES:
return {...state, customJokes: action.payload};
case ActionType.FETCH_JOKES_BY_ID:
return {...state, customJoke: action.payload};
case ActionType.DELETE_CUSTOM_JOKE:
return {...state, jokeId: action.payload};
default:
return state;
}
}
export default customReducer;

@ -1,29 +0,0 @@
import customReducer from './CustomReducer';
import { ActionType } from '../actions/CustomJoke';
import { CustomJoke } from '../../model/CustomJoke';
import {describe, expect, it} from "@jest/globals";
describe('Custom Reducer', () => {
it('should return the initial state', () => {
expect(customReducer(undefined, {})).toEqual({
customJokes: [],
customJoke: null,
jokeId: ''
});
});
it('should handle POST_CUSTOM_JOKE', () => {
const customJoke: CustomJoke = new CustomJoke('1', 'pun', 'Setup', 'https://example.com/image.jpg', 'Punchline');
const action = {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke
};
expect(customReducer(undefined, action)).toEqual({
customJokes: [],
customJoke: customJoke,
jokeId: ''
});
});
});

@ -1,21 +0,0 @@
import {Action, ActionType} from "../actions/DarkModeAction";
interface State {
theme: String
}
const initialState = {
theme: 'dark'
}
const themeReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.SET_THEME:
// @ts-ignore
return {...state, theme: action.payload};
default:
return state;
}
}
export default themeReducer;

@ -1,22 +0,0 @@
import themeReducer from './DarkModeReducer';
import { ActionType } from '../actions/DarkModeAction';
import {describe, expect, it} from "@jest/globals";
describe('Theme Reducer', () => {
// it('should return the initial state', () => {
// expect(themeReducer(undefined, {})).toEqual({
// theme: 'dark'
// });
// });
it('should handle SET_THEME', () => {
const action = {
type: ActionType.SET_THEME,
payload: 'light'
};
expect(themeReducer(undefined, action)).toEqual({
theme: 'light'
});
});
});

@ -1,26 +0,0 @@
import {Action, ActionType} from "../actions/FavoriteAction";
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
interface State {
joke: CustomJoke | SampleJoke
}
const initialState = {
joke: new CustomJoke('', '', '', '', '')
}
const favoriteReducer = (state: State = initialState, action: Action) => {
switch (action.type) {
case ActionType.ADD_FAVORITE:
// @ts-ignore
return {...state, joke: action.payload};
case ActionType.REMOVE_FAVORITE:
// @ts-ignore
return {...state, joke: action.payload};
default:
return state;
}
}
export default favoriteReducer;

@ -1,61 +0,0 @@
// Importez les réducteurs et les types d'action appropriés
import categoryReducer from './CategoryReducer';
import customReducer from './CustomReducer';
import themeReducer from './DarkModeReducer';
import sampleReducer from './SampleReducer';
import { ActionType as CategoryActionType } from '../actions/CategoryAction';
import { ActionType as CustomActionType } from '../actions/CustomJoke';
import { ActionType as ThemeActionType } from '../actions/DarkModeAction';
import { ActionType as SampleActionType } from '../actions/SampleAction';
import { Category } from '../../model/Category';
import { CustomJoke } from '../../model/CustomJoke';
import { SampleJoke } from '../../model/SampleJoke';
import {describe, expect, it} from "@jest/globals";
// Tester categoryReducer
describe('Category Reducer', () => {
it('should return the initial state', () => {
expect(categoryReducer(undefined, {})).toEqual({
categories: []
});
});
// Ajouter d'autres tests pour categoryReducer si nécessaire...
});
// Tester customReducer
describe('Custom Reducer', () => {
it('should return the initial state', () => {
expect(customReducer(undefined, {})).toEqual({
customJokes: [],
customJoke: null,
jokeId: ''
});
});
// Ajouter d'autres tests pour customReducer si nécessaire...
});
// Tester themeReducer
describe('Theme Reducer', () => {
it('should return the initial state', () => {
expect(themeReducer(undefined, {})).toEqual({
theme: 'dark'
});
});
// Ajouter d'autres tests pour themeReducer si nécessaire...
});
// Tester sampleReducer
describe('Sample Reducer', () => {
it('should return the initial state', () => {
expect(sampleReducer(undefined, {})).toEqual({
jokes: [],
favoriteJokes: [],
joke: null,
});
});
// Ajouter d'autres tests pour sampleReducer si nécessaire...
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save