Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/hooks/useEnsureGuestTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SAMPLE_TABLE_DATA } from '../constants/sample_table';
import {
isGuestFlow,
setSessionCustomizeTableData,
} from '../util/sessionStorage';

export const GUEST_TABLE_ID = 'guest';

/**
* 게스트 경로(`/:id === 'guest'`)인데 세션에 테이블 데이터가 없으면
* 샘플 테이블을 세션에 채워 게스트 플로우를 복구한다.
*
* 홈 버튼으로 세션이 비워진 뒤 뒤로가기로 돌아오면 게스트 판별이 풀려
* API 저장소로 `Number('guest')`(NaN) 조회가 나가므로, 쿼리가 저장소를
* 고르기 전에 동기적으로 실행되도록 렌더 중에 처리한다.
* 같은 페이지 인스턴스에서 id만 `guest`로 바뀌는 경우도 막기 위해 매 렌더마다
* 확인하며, 세션 데이터가 있으면 아무것도 하지 않으므로 반복 실행해도 안전하다.
*/
export function useEnsureGuestTable(id: string | undefined) {
if (id === GUEST_TABLE_ID && !isGuestFlow()) {
setSessionCustomizeTableData(SAMPLE_TABLE_DATA);
}
}
2 changes: 2 additions & 0 deletions src/page/TableOverviewPage/TableOverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import {
isSupportedLang,
} from '../../util/languageRouting';
import useAnalytics from '../../hooks/useAnalytics';
import { useEnsureGuestTable } from '../../hooks/useEnsureGuestTable';

// 토론 개요를 보여주고 공유, 수정, 시작 액션을 제공하는 페이지다.
export default function TableOverviewPage() {
const { t, i18n } = useTranslation();
const { id } = useParams();
useEnsureGuestTable(id);
const tableId = Number(id);
const navigate = useNavigate();
const { openModal, closeModal, ModalWrapper } = useModal();
Expand Down
127 changes: 127 additions & 0 deletions src/page/TableOverviewPage/guestBackNavigation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { GlobalPortal } from '../../util/GlobalPortal';
import TableOverviewPage from './TableOverviewPage';
import TableSharingPage from '../TableSharingPage/TableSharingPage';
import { encodeDebateTableData } from '../../util/arrayEncoding';
import { server } from '../../mocks/server';
import { DebateTableData } from '../../types/type';
import { SAMPLE_TABLE_DATA } from '../../constants/sample_table';

const sharedTable: DebateTableData = {
info: {
name: '공유받은 테이블',
agenda: '공유 주제',
prosTeamName: '찬성',
consTeamName: '반대',
},
table: [
{
stance: 'PROS',
speechType: '입론',
boxType: 'NORMAL',
time: 60,
timePerTeam: null,
timePerSpeaking: null,
speaker: '1',
bell: [{ type: 'BEFORE_END', time: 0, count: 2 }],
},
],
};

function renderWithRouter(initialEntry: string) {
const router = createMemoryRouter(
[
{ path: '/share', element: <TableSharingPage /> },
{ path: '/overview/:type/:id', element: <TableOverviewPage /> },
{ path: '/home', element: <div>HOME</div> },
],
{ initialEntries: [initialEntry] },
);

render(
<QueryClientProvider client={new QueryClient()}>
<GlobalPortal.Provider>
<RouterProvider router={router} />
</GlobalPortal.Provider>
</QueryClientProvider>,
);

return router;
}

describe('비회원 테이블 개요 페이지 - 세션 데이터 유실', () => {
const requestedUrls: string[] = [];
const onRequestStart = ({ request }: { request: Request }) => {
requestedUrls.push(request.url);
};

beforeEach(() => {
sessionStorage.clear();
requestedUrls.length = 0;
server.events.on('request:start', onRequestStart);
});

afterEach(() => {
server.events.removeListener('request:start', onRequestStart);
});

it('공유 링크 -> 개요 -> 홈 -> 뒤로가기 시 NaN 조회 없이 샘플 테이블로 복구한다', async () => {
const router = renderWithRouter(
`/share?data=${encodeURIComponent(encodeDebateTableData(sharedTable))}`,
);

await waitFor(() =>
expect(router.state.location.pathname).toBe('/overview/customize/guest'),
);
await screen.findByText('공유받은 테이블');

// 헤더 홈 버튼은 게스트 세션 데이터를 삭제한다.
await userEvent.click(screen.getAllByLabelText('홈으로 이동')[0]);
await waitFor(() => expect(router.state.location.pathname).toBe('/home'));
expect(sessionStorage.getItem('DebateTableData')).toBeNull();

await act(async () => {
await router.navigate(-1);
});
expect(router.state.location.pathname).toBe('/overview/customize/guest');

await screen.findByText(SAMPLE_TABLE_DATA.info.name);
expect(sessionStorage.getItem('DebateTableData')).not.toBeNull();
expect(requestedUrls.filter((url) => url.includes('NaN'))).toEqual([]);
});

it('세션 데이터 없이 게스트 경로로 직접 진입해도 샘플 테이블을 보여준다', async () => {
renderWithRouter('/overview/customize/guest');

await screen.findByText(SAMPLE_TABLE_DATA.info.name);
expect(requestedUrls.filter((url) => url.includes('NaN'))).toEqual([]);
});

it('같은 페이지에서 회원 테이블 id가 게스트로 바뀌어도 샘플 테이블로 복구한다', async () => {
const router = renderWithRouter('/overview/customize/1');
await screen.findByText('나의 자유토론 테이블');

await act(async () => {
await router.navigate('/overview/customize/guest');
});

await screen.findByText(SAMPLE_TABLE_DATA.info.name);
expect(sessionStorage.getItem('DebateTableData')).not.toBeNull();
expect(requestedUrls.filter((url) => url.includes('NaN'))).toEqual([]);
});

it('세션에 게스트 데이터가 있으면 덮어쓰지 않는다', async () => {
sessionStorage.setItem(
'DebateTableData',
JSON.stringify({ id: -1, ...sharedTable }),
);
renderWithRouter('/overview/customize/guest');

await screen.findByText('공유받은 테이블');
expect(screen.queryByText(SAMPLE_TABLE_DATA.info.name)).toBeNull();
});
});
2 changes: 2 additions & 0 deletions src/page/TimerPage/TimerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import clsx from 'clsx';
import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator';
import LoadingIndicator from '../../components/LoadingIndicator/LoadingIndicator';
import { isGuestFlow } from '../../util/sessionStorage';
import { useEnsureGuestTable } from '../../hooks/useEnsureGuestTable';
import useAnalytics from '../../hooks/useAnalytics';
import { consumeTemplateOrigin } from '../../util/analytics/templateOrigin';
import {
Expand Down Expand Up @@ -51,6 +52,7 @@ export default function TimerPage() {
const { t } = useTranslation();
const [answerTime, setAnswerTime] = useState(30);
const pathParams = useParams();
useEnsureGuestTable(pathParams.id);
const tableId = Number(pathParams.id);
const {
openUseTooltipModal,
Expand Down
Loading