Skip to content

Commit 770787a

Browse files
committed
Revert "fix(mission): 추천 생성 복귀 흐름 안정화 (#251) (#252)"
This reverts commit dbafc03.
1 parent dbafc03 commit 770787a

14 files changed

Lines changed: 45 additions & 295 deletions

apps/native/src/bridge.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ const handlers = {
3737
clearGuestTokens: () => guestAuth.clearGuestTokens(),
3838
savePendingMissionGeneration: (job) => pendingMissionGeneration.savePendingMissionGeneration(job),
3939
getPendingMissionGeneration: () => pendingMissionGeneration.getPendingMissionGeneration(),
40-
clearPendingMissionGeneration: (jobId) =>
41-
pendingMissionGeneration.clearPendingMissionGeneration(jobId),
40+
clearPendingMissionGeneration: () => pendingMissionGeneration.clearPendingMissionGeneration(),
4241
hasStartedMissionCreation: () => missionCreationHistory.hasStartedMissionCreation(),
4342
getMissionCreationStartDate: () => missionCreationHistory.getMissionCreationStartDate(),
4443
markMissionCreationStarted: () => missionCreationHistory.markMissionCreationStarted(),

apps/native/src/mission-generation/pending-job.test.ts

Lines changed: 0 additions & 75 deletions
This file was deleted.

apps/native/src/mission-generation/pending-job.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@ import type { PendingMissionGeneration } from "@repo/bridge/types";
22
import * as SecureStore from "expo-secure-store";
33

44
const PENDING_MISSION_GENERATION_KEY = "pending_mission_generation";
5-
let pendingOperation = Promise.resolve();
65

7-
function serializePendingOperation<T>(operation: () => Promise<T>): Promise<T> {
8-
const result = pendingOperation.then(operation, operation);
9-
pendingOperation = result.then(
10-
() => undefined,
11-
() => undefined,
12-
);
13-
return result;
6+
export async function savePendingMissionGeneration(job: PendingMissionGeneration): Promise<void> {
7+
await SecureStore.setItemAsync(PENDING_MISSION_GENERATION_KEY, JSON.stringify(job));
148
}
159

16-
async function readPendingMissionGeneration(): Promise<PendingMissionGeneration | null> {
10+
export async function getPendingMissionGeneration(): Promise<PendingMissionGeneration | null> {
1711
const stored = await SecureStore.getItemAsync(PENDING_MISSION_GENERATION_KEY);
1812
if (!stored) return null;
1913
try {
@@ -31,22 +25,6 @@ async function readPendingMissionGeneration(): Promise<PendingMissionGeneration
3125
}
3226
}
3327

34-
export function savePendingMissionGeneration(job: PendingMissionGeneration): Promise<void> {
35-
return serializePendingOperation(() =>
36-
SecureStore.setItemAsync(PENDING_MISSION_GENERATION_KEY, JSON.stringify(job)),
37-
);
38-
}
39-
40-
export function getPendingMissionGeneration(): Promise<PendingMissionGeneration | null> {
41-
return serializePendingOperation(readPendingMissionGeneration);
42-
}
43-
44-
export function clearPendingMissionGeneration(jobId?: string): Promise<void> {
45-
return serializePendingOperation(async () => {
46-
if (jobId !== undefined) {
47-
const pendingJob = await readPendingMissionGeneration();
48-
if (pendingJob?.jobId !== jobId) return;
49-
}
50-
await SecureStore.deleteItemAsync(PENDING_MISSION_GENERATION_KEY);
51-
});
28+
export function clearPendingMissionGeneration(): Promise<void> {
29+
return SecureStore.deleteItemAsync(PENDING_MISSION_GENERATION_KEY);
5230
}
Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,20 @@
1-
import { beforeEach, describe, expect, it, vi } from "vitest";
2-
import { fireEvent, render, screen, waitFor } from "@/lib/test/react";
3-
4-
const mocks = vi.hoisted(() => ({
5-
getPendingMissionGeneration: vi.fn(),
6-
isNativeApp: vi.fn(),
7-
push: vi.fn(),
8-
}));
9-
10-
vi.mock("@repo/bridge", () => ({
11-
bridge: {
12-
clearPendingMissionGeneration: vi.fn(),
13-
getPendingMissionGeneration: mocks.getPendingMissionGeneration,
14-
},
15-
isNativeApp: mocks.isNativeApp,
16-
}));
17-
18-
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: mocks.push }) }));
19-
1+
import { describe, expect, it, vi } from "vitest";
2+
import { render, screen } from "@/lib/test/react";
203
import { MissionAddMenu } from "./mission-add-menu";
214

225
describe("MissionAddMenu", () => {
23-
beforeEach(() => {
24-
vi.clearAllMocks();
25-
mocks.isNativeApp.mockReturnValue(true);
26-
});
6+
it("플로팅 버튼 영역 밖의 미션 카드를 가로채지 않는다", () => {
7+
render(<MissionAddMenu isOpen={false} onToggle={vi.fn()} />);
278

28-
it("진행 중인 추천 job이 있으면 채팅 화면을 거치지 않고 로딩으로 이동한다", async () => {
29-
mocks.getPendingMissionGeneration.mockResolvedValue({
30-
createdAt: Date.now(),
31-
expiresAt: null,
32-
jobId: "job-1",
33-
});
34-
render(<MissionAddMenu isOpen onToggle={vi.fn()} />);
35-
36-
fireEvent.click(screen.getByRole("link", { name: "추천받기" }));
37-
38-
await waitFor(() =>
39-
expect(mocks.push).toHaveBeenCalledWith("/mission/new/loading?jobId=job-1"),
40-
);
9+
const addButton = screen.getByRole("button", { name: "미션 추가 메뉴 열기" });
10+
expect(addButton.parentElement).toHaveClass("pointer-events-none");
11+
expect(addButton).toHaveClass("pointer-events-auto");
12+
expect(document.getElementById("mission-add-menu")).toHaveClass("pointer-events-none");
4113
});
4214

43-
it("구 버전 네이티브 브릿지에서는 기존 채팅 화면으로 이동한다", async () => {
44-
mocks.getPendingMissionGeneration.mockImplementation(() => {
45-
throw new Error("Method is not defined");
46-
});
15+
it("열린 메뉴의 선택지는 클릭을 받는다", () => {
4716
render(<MissionAddMenu isOpen onToggle={vi.fn()} />);
4817

49-
fireEvent.click(screen.getByRole("link", { name: "추천받기" }));
50-
51-
await waitFor(() => expect(mocks.push).toHaveBeenCalledWith("/mission/new"));
18+
expect(document.getElementById("mission-add-menu")).toHaveClass("pointer-events-auto");
5219
});
5320
});

apps/web/app/(tabs)/mission/_components/mission-add-menu.tsx

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { bridge, isNativeApp } from "@repo/bridge";
21
import { cn } from "@repo/ui";
32
import { Plus } from "lucide-react";
43
import Link from "next/link";
5-
import { useRouter } from "next/navigation";
6-
import { buildMissionLoadingHref } from "@/app/mission/constants/mission-creation";
74

85
interface MissionAddMenuProps {
96
isOpen: boolean;
@@ -13,26 +10,6 @@ interface MissionAddMenuProps {
1310
const MENU_ID = "mission-add-menu";
1411

1512
export function MissionAddMenu({ isOpen, onToggle }: MissionAddMenuProps) {
16-
const router = useRouter();
17-
18-
function handleRecommendationClick(event: React.MouseEvent<HTMLAnchorElement>) {
19-
if (!isNativeApp()) return;
20-
21-
event.preventDefault();
22-
void Promise.resolve()
23-
.then(() => bridge.getPendingMissionGeneration())
24-
.then((job) => {
25-
if (job && (!job.expiresAt || Date.parse(job.expiresAt) > Date.now())) {
26-
router.push(buildMissionLoadingHref(job.jobId));
27-
return;
28-
}
29-
if (job) void bridge.clearPendingMissionGeneration(job.jobId).catch(() => {});
30-
router.push("/mission/new");
31-
})
32-
// 구 버전 네이티브 앱은 이 브릿지 메서드가 없다. 이 경우에는 기존 생성 화면으로 간다.
33-
.catch(() => router.push("/mission/new"));
34-
}
35-
3613
return (
3714
<div className="pointer-events-none fixed inset-x-0 bottom-24 z-10 mx-auto flex w-full max-w-md flex-col items-end gap-3 px-5">
3815
<div
@@ -49,7 +26,6 @@ export function MissionAddMenu({ isOpen, onToggle }: MissionAddMenuProps) {
4926
<Link
5027
className="w-full px-4 py-1 text-left text-body-b1-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
5128
href="/mission/new"
52-
onClick={handleRecommendationClick}
5329
>
5430
추천받기
5531
</Link>

apps/web/app/(tabs)/mission/page.test.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ vi.mock("@/api/mission", () => ({
4646
fetchMissions: vi.fn(),
4747
}));
4848

49-
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
50-
5149
vi.mock("@/app/(tabs)/_components/pigbox-progress-gauge", () => ({
5250
PigboxProgressGauge: ({ playRequest, progress }: { playRequest?: number; progress: number }) => (
5351
<div data-pigbox-play-request={playRequest} data-pigbox-progress={Math.round(progress)} />

apps/web/app/_components/pending-mission-generation-recovery.test.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,11 @@ describe("PendingMissionGenerationRecovery", () => {
107107
const { client, rerender } = renderRecovery();
108108

109109
await vi.waitFor(() => expect(getPendingMissionGeneration).toHaveBeenCalled());
110-
const requestsBeforeReturningToMission = getPendingMissionGeneration.mock.calls.length;
111110
fetchGenerationJobStatus.mockResolvedValue(SUCCEEDED_JOB);
112111
pathname.mockReturnValue("/mission");
113112
rerender(<RecoveryWithClient client={client} />);
114113

115-
await vi.waitFor(() =>
116-
expect(getPendingMissionGeneration.mock.calls.length).toBeGreaterThan(
117-
requestsBeforeReturningToMission,
118-
),
119-
);
114+
await vi.waitFor(() => expect(getPendingMissionGeneration).toHaveBeenCalledTimes(2));
120115
expect(screen.queryByText("미션이 생성됐어요.")).toBeNull();
121116
});
122117

@@ -150,14 +145,4 @@ describe("PendingMissionGenerationRecovery", () => {
150145
await vi.waitFor(() => expect(clearPendingMissionGeneration).toHaveBeenCalled());
151146
expect(replace).not.toHaveBeenCalled();
152147
});
153-
154-
it("구 버전 네이티브 브릿지에 복구 메서드가 없어도 전역 오류를 내지 않는다", async () => {
155-
getPendingMissionGeneration.mockImplementation(() => {
156-
throw new Error("Method is not defined");
157-
});
158-
renderRecovery();
159-
160-
await vi.waitFor(() => expect(getPendingMissionGeneration).toHaveBeenCalled());
161-
expect(replace).not.toHaveBeenCalled();
162-
});
163148
});

apps/web/app/_components/pending-mission-generation-recovery.tsx

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,32 +53,26 @@ export function PendingMissionGenerationRecovery() {
5353

5454
let cancelled = false;
5555
const recoverPendingJob = () => {
56-
void Promise.resolve()
57-
.then(() => bridge.getPendingMissionGeneration())
58-
.then((job) => {
59-
if (cancelled) return;
60-
if (!job) {
61-
setPendingJob(undefined);
62-
return;
63-
}
64-
if (job.expiresAt && Date.parse(job.expiresAt) <= Date.now()) {
65-
setPendingJob(undefined);
66-
void bridge.clearPendingMissionGeneration();
67-
return;
68-
}
69-
if (completedJobId.current === job.jobId) {
70-
setPendingJob(undefined);
71-
return;
72-
}
73-
setPendingJob(job);
74-
if (pathname === "/mission/new") {
75-
router.replace(buildMissionLoadingHref(job.jobId));
76-
}
77-
})
78-
// 웹 배포가 먼저 나간 경우, 구 버전 네이티브에는 해당 브릿지 메서드가 없다.
79-
.catch(() => {
80-
if (!cancelled) setPendingJob(undefined);
81-
});
56+
void bridge.getPendingMissionGeneration().then((job) => {
57+
if (cancelled) return;
58+
if (!job) {
59+
setPendingJob(undefined);
60+
return;
61+
}
62+
if (job.expiresAt && Date.parse(job.expiresAt) <= Date.now()) {
63+
setPendingJob(undefined);
64+
void bridge.clearPendingMissionGeneration();
65+
return;
66+
}
67+
if (completedJobId.current === job.jobId) {
68+
setPendingJob(undefined);
69+
return;
70+
}
71+
setPendingJob(job);
72+
if (pathname === "/mission/new") {
73+
router.replace(buildMissionLoadingHref(job.jobId));
74+
}
75+
});
8276
};
8377

8478
recoverPendingJob();

apps/web/app/mission/_components/mission-creation-result.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function MissionCreationResult({ jobId }: MissionCreationResultProps) {
5656
const [selectedDraftIds, setSelectedDraftIds] = useState<string[]>([]);
5757

5858
useEffect(() => {
59-
void clearPendingMissionGeneration(jobId);
59+
void clearPendingMissionGeneration();
6060
}, [jobId]);
6161

6262
function toggleDraft(id: string, pressed: boolean) {

apps/web/app/mission/new/_components/mission-creation-chat.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ export function MissionCreationChat() {
159159
setSubmitError(undefined);
160160
requestJob.mutate(request, {
161161
onError: () => setSubmitError("미션 생성을 시작하지 못했어요. 잠시 후 다시 시도해 주세요."),
162-
onSuccess: (job) => {
162+
onSuccess: async (job) => {
163163
// 생성 이력은 홈 CTA만 바꾸는 부가 상태다. 구 버전 네이티브 브릿지의 응답을
164164
// 기다리다가 실제 생성 job의 로딩 화면 진입까지 늦추면 안 된다.
165165
void markMissionCreationStarted();
166-
void savePendingMissionGeneration({
166+
await savePendingMissionGeneration({
167167
createdAt: Date.now(),
168168
expiresAt: job.expiresAt,
169169
jobId: job.jobId,

0 commit comments

Comments
 (0)