Compare commits
35 Commits
@ -0,0 +1,17 @@
|
|||||||
|
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
|
@ -0,0 +1,8 @@
|
|||||||
|
> 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.
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"devices": []
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:assert');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:async_hooks');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:buffer');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:child_process');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:cluster');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:console');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:constants');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:crypto');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:dgram');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:diagnostics_channel');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:dns');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:domain');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:events');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:fs');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:fs/promises');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:http');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:http2');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:https');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:inspector');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:module');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:net');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:os');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:path');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:perf_hooks');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:process');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:punycode');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:querystring');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:readline');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:repl');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:stream');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:string_decoder');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:timers');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:tls');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:trace_events');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:tty');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:url');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:util');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:v8');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:vm');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:wasi');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:worker_threads');
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = $$require_external('node:zlib');
|
@ -0,0 +1 @@
|
|||||||
|
global.$$require_external = typeof window === "undefined" ? require : () => null;
|
@ -0,0 +1 @@
|
|||||||
|
global.$$require_external = (moduleId) => {throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);}
|
@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,32 @@
|
|||||||
|
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)',
|
||||||
|
},
|
||||||
|
};
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 604 B |
After Width: | Height: | Size: 209 B |
After Width: | Height: | Size: 315 B |
After Width: | Height: | Size: 603 B |
After Width: | Height: | Size: 744 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 502 B |
After Width: | Height: | Size: 705 B |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 301 B |
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 805 B |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@ -0,0 +1,27 @@
|
|||||||
|
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'
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,21 @@
|
|||||||
|
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}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,178 @@
|
|||||||
|
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'
|
||||||
|
}
|
||||||
|
})
|
@ -0,0 +1,54 @@
|
|||||||
|
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
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,67 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
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({
|
||||||
|
|
||||||
|
})
|
@ -0,0 +1,22 @@
|
|||||||
|
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()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @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"}]');
|
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,61 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,126 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|