Compare commits

..

No commits in common. 'master' and 'part1' 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

@ -59,16 +59,16 @@ Le projet de tp utilise un modèle de flux de travail Git pour organiser le dév
## Avancés du projet
* [x] Partie 0 - Project creation
* [x] Partie 1 - Project organisation
* [X] Partie 2 - Typescript types
* [X] Partie 3 - Screens
* [X] Partie 4 - Components
* [X] Partie 5 - FlatList
* [X] Partie 6 - Safe Area
* [X] Partie 7 - Navigation
* [X] Partie 8 - Hooks
* [X] Partie 9 - Redux Store
* [X] Partie 10 - Async Storage
* [X] Partie 11 - Theming
* [ ] Partie 2 - Typescript types
* [ ] Partie 3 - Screens
* [ ] Partie 4 - Components
* [ ] Partie 5 - FlatList
* [ ] Partie 6 - Safe Area
* [ ] Partie 7 - Navigation
* [ ] Partie 8 - Hooks
* [ ] Partie 9 - Redux Store
* [ ] Partie 10 - Async Storage
* [ ] Partie 11 - Theming
* [ ] Partie 12 - Unit testing
* [ ] 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,74 +0,0 @@
import {StyleSheet, Text, View, Image} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import {darksalmonColor, whiteColor, greyColor, indigoColor} from "../assets/Theme";
import Categ from "./Categ";
type JokeListItemProps = {
joke: (CustomJoke | SampleJoke);
};
export default function JokeItem(prop: JokeListItemProps) {
return (
<View style={styles.rowContainer}>
<View style={styles.color}/>
<Image source={{ uri: prop.joke.image }} style={styles.image} />
<View style={styles.columnContainer}>
<Text style={styles.text}>Résumé de la blague</Text>
<Text style={styles.text}>{prop.joke.description()}</Text>
<View style={styles.bottomContainer}>
<Text style={{color:'white'}}>{prop.joke.type}</Text>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
rowContainer: {
flexDirection: "row",
marginHorizontal: "5%",
marginBottom:7,
marginTop:7,
paddingVertical: 10,
backgroundColor: indigoColor,
width:'90%',
height:150,
justifyContent: 'space-between',
alignItems: 'center',
},
color: {
flex: 0,
backgroundColor: darksalmonColor,
height: 150,
width:15,
},
image: {
width: '40%',
height: 150,
flex: 1
},
columnContainer: {
flexDirection: "column",
marginLeft: 20,
marginRight: 20,
width: '60%',
flex: 2,
justifyContent: 'space-between',
},
text: {
color:whiteColor,
paddingBottom: 7,
paddingTop: 7,
fontSize: 16,
},
bottomContainer: {
backgroundColor: greyColor,
paddingVertical: 5,
paddingHorizontal: 10,
margin: 10,
borderRadius: 20,
width : 120,
alignItems : 'center'
}
});

@ -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,13 +0,0 @@
/**
* @file SampleJokeStub.ts
* @brief Exemple d'utilisation de la classe JokeFactory pour créer des blagues simples.
*/
import { JokeFactory } from '../../model/JokeFactory';
/**
* @brief Stub de blagues simples créées à l'aide de la classe JokeFactory.
* @constant
* @type {SampleJoke[]}
*/
export const sampleJokeStub = JokeFactory.createSampleJokes('[{"id":1, "type":"custom", "setup":"one", "punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"},{"id":2, "type":"custom", "setup":"two","punchline":"y\'en a pas", "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,44 +0,0 @@
/**
* @file CustomJoke.ts
* @brief Définition de la classe CustomJoke.
*/
import { Joke } from './Joke';
/**
* @class
* @brief Représente une blague personnalisée avec un identifiant unique.
* @extends Joke
*/
export class CustomJoke extends Joke {
private _id: string;
/**
* @brief Constructeur de la classe CustomJoke.
* @param {string} id - L'identifiant unique de la blague.
* @param {string} type - Le type de la blague.
* @param {string} setup - La partie préliminaire de la blague.
* @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague.
*/
constructor(id: string, type: string, setup: string, image: string, punchline: string = "") {
super(type, setup, punchline, image); // Assuming Joke class has these properties
this._id = id;
}
/**
* @brief Obtient l'identifiant de la blague.
* @return {string} L'identifiant de la blague.
*/
get id(): string {
return this._id;
}
/**
* @brief Modifie l'identifiant de la blague.
* @param {string} id - Le nouvel identifiant de la blague.
*/
set id(id: string) {
this._id = id;
}
}

@ -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,112 +0,0 @@
/**
* @file Joke.ts
* @brief Définition de la classe abstraite Joke.
*/
/**
* @class
* @brief Classe abstraite représentant une blague.
*/
export abstract class Joke {
private _type: string;
private _setup: string;
private _punchline: string;
private _image: string;
/**
* @brief Constructeur de la classe Joke.
* @param {string} type - Le type de la blague.
* @param {string} setup - La partie préliminaire de la blague.
* @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague.
*/
constructor(type: string, setup: string, punchline: string, image: string) {
this._type = type;
this._setup = setup;
this._punchline = punchline;
this._image = image;
}
/**
* @brief Obtient le type de la blague.
* @return {string} Le type de la blague.
*/
get type() {
return this._type;
}
/**
* @brief Obtient la partie préliminaire de la blague.
* @return {string} La partie préliminaire de la blague.
*/
get setup(): string {
return this._setup;
}
/**
* @brief Obtient la chute de la blague.
* @return {string} La chute de la blague.
*/
get punchline(): string {
return this._punchline;
}
/**
* @brief Obtient l'URL de l'image associée à la blague.
* @return {string} L'URL de l'image associée à la blague.
*/
get image(): string {
return this._image;
}
/**
* @brief Modifie le type de la blague.
* @param {string} theType - Le nouveau type de la blague.
*/
set type(theType: string) {
this._type = theType;
}
/**
* @brief Modifie la partie préliminaire de la blague.
* @param {string} theSetup - La nouvelle partie préliminaire de la blague.
*/
public set setup(theSetup: string) {
this._setup = theSetup;
}
/**
* @brief Modifie la chute de la blague.
* @param {string} thePunchline - La nouvelle chute de la blague.
*/
public set punchline(thePunchline: string) {
this._punchline = thePunchline;
}
/**
* @brief Modifie l'URL de l'image associée à la blague.
* @param {string} theImage - Le nouvel URL de l'image associée à la blague.
*/
public set image(theImage: string) {
this._image = theImage;
}
/**
* @brief Obtient un résumé de la blague.
* @return {string} Un résumé de la blague.
*/
public summary(): string {
if(this.punchline.length <= 25){
return this.punchline;
}
return this.punchline.substring(0, 24) + "...";
}
/**
* @brief Obtient une description textuelle de la blague.
* @return {string} Une description textuelle de la blague.
*/
public description(): string {
return this.type + ", " + this.summary();
}
}

@ -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,45 +0,0 @@
/**
* @file SampleJoke.ts
* @brief Définition de la classe SampleJoke.
*/
import { Joke } from "./Joke";
/**
* @class
* @brief Classe représentant une blague d'échantillon.
* @extends Joke
*/
export class SampleJoke extends Joke {
private _id: number;
/**
* @brief Constructeur de la classe SampleJoke.
* @param {number} id - L'identifiant de la blague d'échantillon.
* @param {string} type - Le type de la blague.
* @param {string} setup - La partie préliminaire de la blague.
* @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague.
*/
constructor(id: number, type: string, setup: string, image: string, punchline: string = "") {
super(type, setup, punchline, image); // Assuming Joke class has these properties
this._id = id;
}
/**
* @brief Obtient l'identifiant de la blague d'échantillon.
* @return {number} L'identifiant de la blague d'échantillon.
*/
get id(): number {
return this._id;
}
/**
* @brief Modifie l'identifiant de la blague d'échantillon.
* @param {number} id - Le nouvel identifiant de la blague d'échantillon.
*/
set id(id: number) {
this._id = id;
}
}

@ -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;

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

Loading…
Cancel
Save