From 99ded4dfd75e1204c9ed19d306b1330f88fb4a2d Mon Sep 17 00:00:00 2001 From: haotool Date: Sat, 27 Jun 2026 18:51:54 +0800 Subject: [PATCH] =?UTF-8?q?test(ratewise):=20=E8=A3=9C=E9=9B=A2=E7=B7=9A?= =?UTF-8?q?=E9=9D=9C=E6=85=8B=E8=B3=87=E6=BA=90=E4=B8=89=E5=B1=A4=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E5=96=AE=E5=85=83=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 resolveOfflineStaticResourceFallback 四項測試 - 覆蓋 exact、ignoreSearch、matchPrecache 與全 miss 回退 - 對齊 PR 452 未覆蓋的離線關鍵路徑 測試:pnpm --filter @app/ratewise exec vitest run pwaOfflineFallback、typecheck、lint Co-authored-by: Cursor --- .../__tests__/pwaOfflineFallback.test.ts | 77 ++++++++++++++++++- .../dev/002_development_reward_penalty_log.md | 7 +- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/apps/ratewise/src/utils/__tests__/pwaOfflineFallback.test.ts b/apps/ratewise/src/utils/__tests__/pwaOfflineFallback.test.ts index a50832d65..ee5ddedc7 100644 --- a/apps/ratewise/src/utils/__tests__/pwaOfflineFallback.test.ts +++ b/apps/ratewise/src/utils/__tests__/pwaOfflineFallback.test.ts @@ -1,6 +1,9 @@ -import { describe, expect, it, vi } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { APP_INFO } from '../../config/app-info'; -import { resolveOfflineDocumentFallback } from '../pwaOfflineFallback'; +import { + resolveOfflineDocumentFallback, + resolveOfflineStaticResourceFallback, +} from '../pwaOfflineFallback'; describe('pwaOfflineFallback', () => { it('should prefer cached index.html before offline fallbacks', async () => { @@ -82,3 +85,73 @@ describe('pwaOfflineFallback', () => { expect(response.headers.get('X-RateWise-Offline-Fallback')).not.toBeNull(); }); }); + +describe('resolveOfflineStaticResourceFallback', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('should return exact caches.match(request) hit without calling matchPrecacheFn', async () => { + const request = new Request('https://app.haotool.org/ratewise/assets/x.js'); + const exactResponse = new Response('exact'); + const cachesMatch = vi.fn().mockResolvedValue(exactResponse); + vi.stubGlobal('caches', { match: cachesMatch }); + const matchPrecacheFn = vi.fn(); + + const response = await resolveOfflineStaticResourceFallback(request, matchPrecacheFn); + + expect(response).toBe(exactResponse); + expect(cachesMatch).toHaveBeenCalledTimes(1); + expect(cachesMatch).toHaveBeenCalledWith(request); + expect(matchPrecacheFn).not.toHaveBeenCalled(); + }); + + it('should fall back to ignoreSearch match when exact match misses', async () => { + const request = new Request('https://app.haotool.org/ratewise/assets/x.js?__WB_REVISION__=abc'); + const ignoreSearchResponse = new Response('ignore-search'); + const cachesMatch = vi + .fn() + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(ignoreSearchResponse); + vi.stubGlobal('caches', { match: cachesMatch }); + const matchPrecacheFn = vi.fn(); + + const response = await resolveOfflineStaticResourceFallback(request, matchPrecacheFn); + + expect(response).toBe(ignoreSearchResponse); + expect(cachesMatch).toHaveBeenCalledTimes(2); + expect(cachesMatch).toHaveBeenNthCalledWith(1, request); + expect(cachesMatch).toHaveBeenNthCalledWith(2, 'https://app.haotool.org/ratewise/assets/x.js', { + ignoreSearch: true, + }); + expect(matchPrecacheFn).not.toHaveBeenCalled(); + }); + + it('should fall back to matchPrecacheFn when caches layers miss', async () => { + const request = new Request('https://app.haotool.org/ratewise/assets/x.js'); + const precacheResponse = new Response('precached'); + const cachesMatch = vi.fn().mockResolvedValue(undefined); + vi.stubGlobal('caches', { match: cachesMatch }); + const matchPrecacheFn = vi.fn((key: string) => (key === 'x.js' ? precacheResponse : null)); + + const response = await resolveOfflineStaticResourceFallback(request, matchPrecacheFn); + + expect(response).toBe(precacheResponse); + expect(matchPrecacheFn.mock.calls.map(([key]) => key)).toEqual([ + 'ratewise/assets/x.js', + 'assets/x.js', + 'x.js', + ]); + }); + + it('should return Response.error() when all fallback layers miss', async () => { + const request = new Request('https://app.haotool.org/ratewise/assets/x.js'); + const cachesMatch = vi.fn().mockResolvedValue(undefined); + vi.stubGlobal('caches', { match: cachesMatch }); + const matchPrecacheFn = vi.fn().mockResolvedValue(null); + + const response = await resolveOfflineStaticResourceFallback(request, matchPrecacheFn); + + expect(response.type).toBe('error'); + }); +}); diff --git a/docs/dev/002_development_reward_penalty_log.md b/docs/dev/002_development_reward_penalty_log.md index 12951d29e..057351a21 100644 --- a/docs/dev/002_development_reward_penalty_log.md +++ b/docs/dev/002_development_reward_penalty_log.md @@ -2,7 +2,7 @@ > 版本:outline-v2-ultra > 原則:每筆只保留日期、ID、原因、解法。 -> 本次分數變化:+1(reward 1、penalty 0、neutral 0)|累計總分:+69 +> 本次分數變化:+1(reward 1、penalty 0、neutral 0)|累計總分:+70 ## 新增模板(4 行) @@ -13,6 +13,11 @@ ## 條目(新→舊) +- 日期:2026-06-27 +- ID:reward-pwa-static-fallback-unit-tests +- 原因:PR 452 未覆蓋 resolveOfflineStaticResourceFallback 三層離線回退路徑 +- 解法:補 exact、ignoreSearch、matchPrecache 與全 miss 四項單元測試 + - 日期:2026-06-27 - ID:reward-moneybox-workflow-api-semantics-checkout - 原因:data branch workflow 只 checkout fetch 腳本,缺 api-semantics-v2.ts 導致 enrich 失敗且 CDN 無 v2