LifeSports Application(ReactNative & Nest.js) - 27. message-service(3)

yellow_note·2021년 10월 28일
0

#1 시나리오 케이스

케이스에 따라 구현을 진행해보도록 하겠습니다.

1) 게시글(첫 메시지): 게시글을 이용해 메시지를 보내는 경우에는 게시글 작성자 state인 writer를 MessageRoom 컴포넌트로 보내어 메시지를 작성할 수 있습니다. 해당 경우에는 initRoom 메서드를 이용하여 채팅방을 생성하도록 합니다.

2) 게시글(첫 메시지 이후): 게시글을 통해 메시지를 보내는 경우 유저 간의 채팅방이 존재하는 경우가 있습니다. 이와 같은 경우 기존 채팅방을 불러옵니다.

3) 메시지 메인 컴포넌트에서 채팅방으로 넘어가는 경우: 해당 경우에도 유저 간 채팅방이 존재하니 기존의 채팅방을 불러옵니다.

#2 redux 모듈

앞서 작성한 message-service와 react native 연결을 위한 리덕스 모듈을 작성해보도록 하겠습니다. 이번에 작성한 모듈은 총 2개입니다. 메시지 전송과 채팅방 삭제를 담당하는 message 모듈, 채팅방과 채팅방들을 가져오기 위한 rooms 모듈입니다. 모듈 작성 이전에 api부터 작성해보도록 하겠습니다.

다음의 라이브러리를 설치하도록 하겠습니다.

npm install --save qs
  • axios get
    postman으로 테스트를 진행할 때 body에 값을 실어도 문제 없이 테스트가 진행되었으나 axios에서는 get 요청시 body를 지워버리기 때문에 query string으로 변경하도록 하겠습니다.
  • ./src/lib/api/message.js
import client from './client';

export const initRoom = ({
    sender,
    receiver
}) => client.post('http://10.0.2.2:8000/message-service/room/init-room', {
    sender,
    receiver
});

export const sendMessage = ({
    sender,
    receiver,
    content
}) => client.post('http://10.0.2.2:8000/message-service/message', {
    sender,
    receiver,
    content
});

export const getRoom = roomId => client.get(`http://10.0.2.2:8000/message-service/${roomId}/room`);

export const getRooms = nickname => client.get(`http://10.0.2.2:8000/message-service/${nickname}/rooms`);

export const getRoomsByKeyword = keyword => client.get(`http://10.0.2.2:8000/message-service/${keyword}/keyword/rooms`);

export const deleteRoom = roomId => client.delete(`http://10.0.2.2:8000/message-service/${roomId}/room`);

export const checkRoom = ({
    sender,
    receiver
}) => client.get('http://10.0.2.2:8000/message-service/room/check', {
    sender,
    receiver
});

api들을 작성했으니 메시지를 보내기 위한 message모듈을 작성해보도록 하겠습니다.

  • ./src/modules/message.js
import { createAction, handleActions } from "redux-actions";
import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
import * as messageAPI from '../lib/api/message';
import { takeLatest } from "@redux-saga/core/effects";

const INITIALIZE = 'message/INITIALIZE';

const CHANGE_FIELD = 'message/CHANGE_FIELD';

const [
    SEND_MESSAGE,
    SEND_MESSAGE_SUCCESS,
    SEND_MESSAGE_FAILURE
] = createRequestActionTypes('message/SEND_MESSAGE');

export const initialize = createAction(INITIALIZE, form => form);

export const changeField = createAction(CHANGE_FIELD, ({
    key,
    value
}) => ({
    key,
    value
}));

export const sendMessage = createAction(SEND_MESSAGE, ({
    sender,
    receiver,
    content,
}) => ({
    sender,
    receiver,
    content
}));

const sendMessageSaga = createRequestSaga(SEND_MESSAGE, messageAPI.sendMessage);

export function* messageSaga() {
    yield takeLatest(SEND_MESSAGE, sendMessageSaga);
};

const initialState = {
    sender: null,
    receiver: null,
    content: null,
    result: null,
    error: null,
};

const message = handleActions(
    {
        [INITIALIZE]: (state, { payload: form }) => ({
            ...state,
            [form]: initialState[form]
        }),
        [CHANGE_FIELD]: (state, { payload: { key, value }}) => ({
            ...state,
            [key]: value
        }),
        [SEND_MESSAGE_SUCCESS]: (state, { payload: result }) => ({
            ...state,
            result
        }),
        [SEND_MESSAGE_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error,
        })
    },
    initialState
);

export default message;

채팅방과 관련된 모듈입니다. 이 모듈에선 checkRoom, initRoom, getRoom, DeleteRoom의 메서드를 액션함수로 만들도록 하겠습니다.

  • ./src/modules/room.js
import { createAction, handleActions } from "redux-actions";
import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
import * as messageAPI from '../lib/api/message';
import { takeLatest } from "@redux-saga/core/effects";

const INITIALIZE = 'room/INITIALIZE';

const CHANGE_FIELD = 'room/CHANGE_FIELD';

const [
    INIT_ROOM,
    INIT_ROOM_SUCCESS,
    INIT_ROOM_FAILURE
] = createRequestActionTypes('room/INIT_ROOM');

const [
    GET_ROOM,
    GET_ROOM_SUCCESS,
    GET_ROOM_FAILURE
] = createRequestActionTypes('room/GET_ROOM');

const [
    CHECK_ROOM,
    CHECK_ROOM_SUCCESS,
    CHECK_ROOM_FAILURE
] = createRequestActionTypes('room/CHECK_ROOM');

const [
    DELETE_ROOM,
    DELETE_ROOM_SUCCESS,
    DELETE_ROOM_FAILURE
] = createRequestActionTypes('room/DELETE_ROOM');

export const initialize = createAction(INITIALIZE, form => form);

export const changeField = createAction(CHANGE_FIELD, ({
    key,
    value
}) => ({
    key,
    value
}));

export const initRoom = createAction(INIT_ROOM, ({
    user_a,
    user_b
}) => ({
    user_a,
    user_b
}));

export const getRoom = createAction(GET_ROOM, roomId => roomId);

export const checkRoom = createAction(CHECK_ROOM, ({
    user_a,
    user_b
}) => ({
    user_a,
    user_b
}));

export const deleteRoom = createAction(DELETE_ROOM, roomId => roomId);

const initRoomSaga = createRequestSaga(INIT_ROOM, messageAPI.initRoom);

const checkRoomSaga = createRequestSaga(CHECK_ROOM, messageAPI.checkRoom);

const getRoomSaga = createRequestSaga(GET_ROOM, messageAPI.getRoom);

const deleteRoomSaga = createRequestSaga(DELETE_ROOM, messageAPI.deleteRoom);

export function* roomSaga() {
    yield takeLatest(INIT_ROOM, initRoomSaga);
    yield takeLatest(CHECK_ROOM, checkRoomSaga);
    yield takeLatest(GET_ROOM, getRoomSaga);
    yield takeLatest(DELETE_ROOM, deleteRoomSaga);
}; 

const initialState = {
    user_a: null,
    user_b: null,
    roomId: 'DEFAULT_ID',
    room: null,
    result: null,
    error: null
};

const room = handleActions(
    {
        [INITIALIZE]: (state, { payload: form }) => ({
            ...state,
            [form]: initialState[form]
        }),
        [CHANGE_FIELD]: (state, { payload: { key, value }}) => ({
            ...state,
            [key]: value
        }),
        [INIT_ROOM_SUCCESS]: (state, { payload: roomId }) => ({
            ...state,
            roomId
        }),
        [INIT_ROOM_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
        [GET_ROOM_SUCCESS]: (state, { payload: room }) => ({
            ...state,
            room
        }),
        [GET_ROOM_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
        [CHECK_ROOM_SUCCESS]: (state, { payload: roomId }) => ({
            ...state,
            roomId
        }),
        [CHECK_ROOM_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
        [DELETE_ROOM_SUCCESS]: (state, { payload: result }) => ({
            ...state,
            result
        }),
        [DELETE_ROOM_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        })
    },
    initialState
);

export default room;

채팅방들을 호출하기 위한 모듈입니다.

  • ./src/modules/rooms.js
import { createAction, handleActions } from "redux-actions";
import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
import * as messageAPI from '../lib/api/message';
import { takeLatest } from "@redux-saga/core/effects";

const [
    GET_ROOMS,
    GET_ROOMS_SUCCESS,
    GET_ROOMS_FAILURE
] = createRequestActionTypes('rooms/GET_ROOMS');

const [
    GET_ROOMS_KEYWORD,
    GET_ROOMS_KEYWORD_SUCCESS,
    GET_ROOMS_KEYWORD_FAILURE
] = createRequestActionTypes('rooms/GET_ROOMS_KEYWORD');

export const getRooms = createAction(GET_ROOMS, nickname => nickname);

export const getRoomsKeyword = createAction(GET_ROOMS_KEYWORD, keyword => keyword);

const getRoomsSaga = createRequestSaga(GET_ROOMS, messageAPI.getRooms);

const getRoomsByKeywordSaga = createRequestSaga(GET_ROOMS_KEYWORD, messageAPI.getRoomsByKeyword);

export function* roomsSaga() {
    yield takeLatest(GET_ROOMS, getRoomsSaga);
    yield takeLatest(GET_ROOMS_KEYWORD, getRoomsByKeywordSaga);
};

const initialState = {
    rooms: null,
    error: null
};

const rooms = handleActions(
    {
        [GET_ROOMS_SUCCESS]: (state, { payload: rooms }) => ({
            ...state,
            rooms
        }),
        [GET_ROOMS_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
        [GET_ROOMS_KEYWORD_SUCCESS]: (state, { payload: rooms }) => ({
            ...state,
            rooms
        }),
        [GET_ROOMS_KEYWORD_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
    },
    initialState,
);

export default rooms;

리덕스 모듈들이 완성되었습니다. 그러면 index.js에 모듈들을 임포트하도록 하겠습니다.

  • ./src/modules/index.js
import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import auth, { authSaga } from './auth';
import loading from "./loading";
import user, { userSaga } from "./user";
import marker from './marker';
import map, { mapSaga } from "./map";
import maps, { mapsSaga } from "./maps";
import rental, { rentalSaga } from './rental';
import payment, { paymentSaga } from './payment';
import payments, { paymentsSaga } from "./payments";
import rentals, { rentalsSaga } from "./rentals";
import post, { postSaga } from "./post";
import posts, { postsSaga } from "./posts";
import comment, { commentSaga } from "./comment";
import message, { messageSaga } from "./message";
import rooms, { roomsSaga } from "./rooms";
import room, { roomSaga } from "./room";

const rootReducer = combineReducers({
    auth,
    loading,
    user,
    marker,
    map,
    maps,
    rental,
    rentals,
    payment,
    payments,
    post,
    posts,
    comment,
    message,
    room,
    rooms
});

export function* rootSaga() {
    yield all([
        authSaga(),
        userSaga(),
        mapSaga(),
        mapsSaga(),
        rentalSaga(),
        rentalsSaga(),
        paymentSaga(),
        paymentsSaga(),
        postSaga(),
        postsSaga(),
        commentSaga(),
        messageSaga(),
        roomSaga(),
        roomsSaga()
    ]);
};

export default rootReducer;

모듈 관련 코드가 완성되었으니 컴포넌트에서 모듈들을 호출해보도록 하겠습니다.

#3 컴포넌트, 모듈 연동

시나리오 케이스에 따라 컴포넌트와 모듈을 연동해보겠습니다. 가장 우선 게시글을 통해 넘어온 writer의 값을 receiver로, 현재 로그인한 유저의 nickname을 sender로 state를 저장시켜야겠죠.

그리고 checkRoom을 통하여 결과값을 확인한 뒤, roomId값이 반환된다면 getRoom메서드를 통해 기존의 채팅방을 불러오거나 null값이 반환된다면 initRoom을 통해 채팅방을 만들어야 겠습니다.

  • ./src/pages/message/RoomScreen.js
import { useRoute } from '@react-navigation/core';
import React, { useEffect } from 'react';
import { StyleSheet, ScrollView, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, checkRoom, getRoom, initRoom } from '../../modules/room';
import RoomFooter from './components/RoomFooter';
import RoomFragment from './components/RoomFragment';
import RoomHeader from './components/RoomHeader';
import Loading from '../../styles/common/Loading';

const RoomScreen = () => {
    const route = useRoute();
    const dispatch = useDispatch();
    const { 
        nickname,
        user_a,
        user_b,
        roomId,
        room
    } = useSelector(({ 
        user,
        room
    }) => ({ 
        nickname: user.user.nickname,
        user_a: room.user_a,
        user_b: room.user_b,
        roomId: room.roomId,
        room: room.room
    }));

    useEffect(() => {
        dispatch(changeField({
            key: 'user_a',
            value: nickname,
        }));
    }, [dispatch, nickname]);

    useEffect(() => {
        dispatch(changeField({
            key: 'user_b',
            value: route.params.writer
        }))
    }, [dispatch, route.params.writer]);

    useEffect(() => {
        if(user_a && user_b) {
            dispatch(checkRoom({
                user_a,
                user_b
            }));
        }
    }, [dispatch, user_a, user_b]);

    useEffect(() => {
        if(roomId) {
            dispatch(getRoom(roomId));
        }
    }, [dispatch, roomId]);

    useEffect(() => {
        if(user_a && user_b) {
            if(!roomId) {
                dispatch(initRoom({
                    user_a,
                    user_b
                }));
            }
        }
    }, [dispatch, user_a, user_b, roomId]);

    return(
        <View style={ styles.container }>
            {
                room ?
                <View style={ styles.container }>
                    <RoomHeader />
                    <RoomFragment />
                    <RoomFooter />
                </View> :
                <Loading />
            }
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1
    }
});

export default RoomScreen;

코드가 복잡하니 순서대로 따라가보도록 하겠습니다. 우선 useRoute훅을 이용하여 route를 만들어 전달받은 writer 파라미터를 받습니다. 그리고 useSelector로 현재 state에 접근하여 nickname값을 받아옵니다. 이렇게 writer, nickname값을 받아 각각 user_a, user_b room의 state에 저장합니다.

그리고 user_a, user_b가 존재한다면 checkRoom을 호출하여 기존의 채팅방이 있는지 확인합니다. 이로 인해 받은 roomId값이 존재한다면 이 값으로 getRoom을 호출하고, 그렇지 않다면 initRoom을 호출합니다.

이 과정들을 끝내고 나면 자동적으로 room state에 채팅방에 대한 결과값이 담기게 되겠죠.

  • ./src/pages/message/components/RoomHeader.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useSelector } from 'react-redux';
import palette from '../../../styles/palette';

const RoomHeader = () => {
    const { 
        room, 
        nickname 
    } = useSelector(({ 
        room,
        user 
    }) => ({
        room: room.room, 
        nickname: user.user.nickname 
    }));
    const otherMan = room.users[0] === nickname ? room.users[1] : room.users[0];

    return(
        <View style={ styles.container }>
            <Text style={ styles.font }>
                { otherMan } 님과의 채팅방
            </Text>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 0.1,
        backgroundColor: palette.white[0],
        padding: 10,
        borderBottomColor: palette.gray[2],
        borderBottomWidth: 2,
        justifyContent: 'center'
    },
    font: {
        fontSize: 18,
        fontWeight: 'bold'
    }
});

export default RoomHeader;

useSelector로 room state에 접근한 후 users 배열에 현재 로그인한 유저와는 다른 닉네임 값을 가져와 채팅방의 이름을 보여주도록 합니다.

  • ./src/pages/message/components/RoomFragment.js
import React from 'react';
import { StyleSheet, ScrollView } from 'react-native';
import { useSelector } from 'react-redux';
import palette from '../../../styles/palette';
import MessageCard from './MessageCard';

const RoomFragment = () => {
    const { room } = useSelector(({ room }) => ({ room: room.room }));

    return(
        <ScrollView style={ styles.container }>
            {
                room.messages.map(message => { return <MessageCard message={ message } /> })
            }
        </ScrollView>
    );
};

...

export default RoomFragment;

room state에 접근하여 메시지 내역들을 보여주도록 합니다.

  • ./src/pages/message/components/MessageCard.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useSelector } from 'react-redux';
import palette from '../../../styles/palette';

const MessageCard = message => {
    const { nickname } = useSelector(({ user }) => ({ nickname: user.user.nickname }));
    message = message.message;

    return(
        <View style={ styles.container }>
            <View style={
                nickname === message.sender ?
                styles.send_date : styles.recevie_date
            }>
                <Text>
                    { message.createdAt.substring(0, 21) }
                </Text>
            </View>
            <View style={ 
                nickname === message.sender ?
                styles.send : styles.receiver
            }>
                <Text style={
                    nickname === message.sender ? 
                    styles.sender_font : styles.receiver_font
                }>
                    { message.content }
                </Text>
            </View>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        margin: 10,
    },
    send: {
        padding: 10,
        marginLeft: 100,
        backgroundColor: palette.blue[0],
        borderColor: palette.blue[0],
        borderWidth: 1,
        borderRadius: 10
    },
    sender_font: {
        color: palette.white[0]
    },
    send_date: {
        marginLeft: 240,
    },
    receiver: {
        flex: 1,
        marginRight: 100,
        padding: 10,
        backgroundColor: palette.white[0],
        borderColor: palette.blue[0],
        borderWidth: 1,
        borderRadius: 10,
    },
    receiver_font: {
        color: palette.blue[0]
    },
    recevie_date: {
        marginLeft: 10
    }
});

export default MessageCard;

전달받은 item의 값에서 데이터를 가져와 삼항연산자를 이용하여 sender, receiver로 나눈 css를 적용합니다.

  • ./src/pages/message/components/MessageInput.js
import React from 'react';
import { Dimensions, StyleSheet, TextInput, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { changeField } from '../../../modules/message';
import palette from '../../../styles/palette';
import SendButton from './SendButton';

const MessageInput = () => {
    const dispatch = useDispatch();
    const { content } = useSelector(({ message }) => ({ content: message.content }));
    const onChange = e => {
        dispatch(changeField({
            key: 'content',
            value: e.nativeEvent.text
        }));
    };
    
    return(
        <View style={ styles.container }>
            <TextInput style={ styles.input }
                       multiline={ true }
                       onChange={ onChange }
                       value={ content }
            />
            <SendButton />
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        width: Dimensions.get('window').width,
        flexDirection: 'row',
    },
    input: {
        width: '90%',
        backgroundColor: palette.gray[0],
    },
});

export default MessageInput;

메시지를 전송하기 위해 onChange메서드를 만들어 입력한 채팅 내용이 content에 담기도록 합니다.

  • ./src/pages/message/components/SendButton.js
import React, { useEffect, useState } from 'react';
import { useRoute } from '@react-navigation/core';
import Icon from 'react-native-vector-icons/Ionicons';
import { StyleSheet, TouchableOpacity } from 'react-native';
import palette from '../../../styles/palette';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, initialize, sendMessage } from '../../../modules/message';
import { getRoom } from '../../../modules/room';

const SendButton = () => {
    const dispatch = useDispatch();
    const route = useRoute();
    const {
        sender,
        receiver,
        content,
        result,
        roomId,
        nickname
    } = useSelector(({ 
        message,
        room,
        user 
    }) => ({
        sender: message.sender,
        receiver: message.receiver,
        content: message.content,
        result: message.result,
        roomId: room.roomId,
        nickname: user.user.nickname
    }));
    const onSend = e => {
        dispatch(sendMessage({
            sender,
            receiver,
            content
        }));

        setRefresh(true);
    }
    const [refresh, setRefresh] = useState(false);

    useEffect(() => {
        dispatch(changeField({
            key: 'sender',
            value: nickname
        }));
    }, [dispatch, nickname]);

    useEffect(() => {
        dispatch(changeField({
            key: 'receiver',
            value: route.params.writer
        }));
    }, [dispatch, route.params.writer]);

    useEffect(() => {
        setRefresh(false);
        
        dispatch(initialize('content'));

        dispatch(getRoom(roomId));
    }, [dispatch, refresh]);

    return <TouchableOpacity style={ styles.button }
                             onPress={ onSend }
           >
                <Icon name={ "ios-paper-plane" } 
                      color={ palette.blue[0] }
                      size={ 30 }
                />
           </TouchableOpacity>
};

const styles = StyleSheet.create({
    button: {
        justifyContent: 'center',
        backgroundColor: palette.gray[0]
    }
});

export default SendButton

nickname, writer의 값을 받아와 각각 sender, receiver state에 저장되도록 changeField메서드를 사용합니다. 이제 최종적으로 저장된 state들을 가져와 sendMessage에 담아 메시지 전송을 시도합니다. 메시지가 성공적으로 전송된다면 useState를 이용한 refresh값을 이용하여 자동적으로 업데이트되도록 합니다.

마지막으로 채팅창 메인화면 컴포넌트를 수정하도록 하겠습니다.

  • ./src/pages/message/components/MessageFragment.js
import React, { useEffect } from 'react';
import { ScrollView, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { getRooms } from '../../modules/rooms';
import Loading from '../../styles/common/Loading';
import MessageFragment from './components/MessageFragment';
import MessageHeader from './components/MessageHeader';

const MessageScreen = () => {
    const dispatch = useDispatch();
    const { 
        nickname,
        rooms
    } = useSelector(({ 
        user,
        rooms    
    }) => ({ 
        nickname: user.user.nickname,
        rooms: rooms.rooms
    }));

    useEffect(() => {
        dispatch(getRooms(nickname));
    }, [dispatch, nickname]);

    return(
        <ScrollView>
            {
                rooms ?
                <View>
                    <MessageHeader />
                    <MessageFragment />
                </View> : <Loading />
            }
        </ScrollView>
    );
};

export default MessageScreen;

현재 로그인한 user의 nickname 값을 가져와 getRooms 메서드를 호출합니다. 유저와 관련된 채팅방들이 있다면 채팅방들을 나열하기 위한 RoomCard 컴포넌트가 그렇지 않다면 Loading 컴포넌트가 호출되겠죠.

  • ./src/pages/message/components/MessageRoomCard.js
import { useNavigation } from '@react-navigation/native';
import React from 'react';
import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { deleteRoom } from '../../../modules/room';
import palette from '../../../styles/palette';

const RoomCard = item => {
    const navigation = useNavigation();
    const dispatch = useDispatch();
    const { nickname } = useSelector(({ user }) => ({ nickname: user.user.nickname }));
    const toRoom = e => {
        navigation.navigate("MessageRoom", {
            writer: item.item.users[0] === nickname ? item.item.users[1] : item.item.users[0]
        });
    };
    const onDelete = e => {
        dispatch(deleteRoom(item.item.roomId));
    };
    const onAlert = e => {
        Alert.alert(
            '채팅방을 나가시겠어요?',
            null,
            [
                {
                    text: '네, 나갈래요',
                    onPress: () => { onDelete }
                },
                {
                    text: '아니요',
                    onPress: () => { return; }
                }
            ]
        );
    };

    return(
        <View style={ styles.container }>
            <TouchableOpacity onPress={ toRoom }
                              onLongPress={ onAlert } 
            >
                <View style={ styles.row }>
                    <Text>
                        { item.item.messages[item.item.messages.length - 1].createdAt.substring(0, 24) }
                    </Text>
                </View>
                <View style={ styles.row }>
                    <Text style={ styles.font }>
                        { 
                            item.item.messages[0].sender === nickname ?
                            item.item.messages[0].receiver : item.item.messages[0].sender
                        } 님 과의 메시지
                    </Text>
                </View>
            </TouchableOpacity>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: palette.white[0],
        padding: 10,
        margin: 10
    },
    row: {
        margin: 5
    },
    font: {
        fontSize: 15,
        fontWeight: 'bold'
    }
});

export default RoomCard;

이로써 채팅과 관련된 모든 컴포넌트들을 연동하였습니다. 그러면 테스트 시나리오를 구성해보고 테스트를 진행해보도록 하겠습니다.

#4 테스트

  • message-service - app.controller.ts - initRoom
    initRoom 메서드의 반환값을 다음과 같이 바꾸도록 하겠습니다.
...

@Controller("message-service")
export class AppController{
    constructor(private readonly messageService: MessageService) {}

    @Post('room/init-room')
    public async initRoom(@Body() vo: RequestInitRoom): Promise<any> {
        try {  
            ...

            return await Object.assign({
                status: HttpStatus.CREATED,
                payload: dto.payload.roomId,
                message: "Successful make room",
            });
        ...
    }

    ...
}

1) 게시글(첫 메시지): 게시글을 이용해 메시지를 보내는 경우에는 게시글 작성자 state인 writer를 MessageRoom 컴포넌트로 보내어 메시지를 작성할 수 있습니다. 해당 경우에는 initRoom 메서드를 이용하여 채팅방을 생성하도록 합니다.

i) biuea 닉네임을 가진 유저가 asd 닉네임을 가진 유저와 채팅을 하기 위해 버튼을 클릭합니다.

ii) checkRoom -> null -> initRoom -> getRoom의 케이스로 수행이 됩니다.

iii) 메시지를 전송합니다.

2) 게시글(첫 메시지 이후): 게시글을 통해 메시지를 보내는 경우 유저 간의 채팅방이 존재하는 경우가 있습니다. 이와 같은 경우 기존 채팅방을 불러옵니다.

i) biuea 닉네임을 가진 유저가 asd 닉네임을 가진 유저와 채팅을 하기 위해 버튼을 클릭합니다.

ii) checkRoom -> roomId -> getRoom의 케이스로 수행이 됩니다.

iii) 메시지를 전송합니다.

3) 메시지 메인 컴포넌트에서 채팅방으로 넘어가는 경우: 해당 경우에도 유저 간 채팅방이 존재하니 기존의 채팅방을 불러옵니다
i) asd 닉네임을 가진 유저가 biuea 닉네임을 가진 유저와 채팅을 하기 위해 메시지 컴포넌트로 이동합니다.

ii) getRooms로 채팅방들을 불러옵니다.

iii) biuea와의 채팅방 카드를 클릭하면 checkRoom -> roomId -> getRoom의 케이스로 수행이 됩니다.

iv) 메시지를 전송합니다.

시나리오대로의 테스트가 잘 진행되었습니다. 특정 케이스에서 에러가 존재하긴 하지만 우선 큰 시나리오가 잘 수행되므로 자잘한 에러케이스들은 추후에 고치도록 하겠습니다.

다음 포스트에서는 홈 화면의 카테고리 별 클릭 이벤트를 구현해보도록 하겠습니다.

0개의 댓글

관련 채용 정보