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
|
@ -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)',
|
||||
},
|
||||
};
|
Before Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 209 B |
Before Width: | Height: | Size: 315 B |
Before Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 744 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 502 B |
Before Width: | Height: | Size: 705 B |
Before Width: | Height: | Size: 301 B |
Before Width: | Height: | Size: 265 B |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 805 B |
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;
|