[{"data":1,"prerenderedAt":627},["ShallowReactive",2],{"navigation_docs_en":3,"-en-development-testing":191,"-en-development-testing-surround":622},[4,61,127,171],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":60},"Guide","i-lucide-book-open","\u002Fen\u002Fguide","en\u002F1.guide",[10,15,20,25,30,35,40,45,50,55],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fguide\u002Fintroduction","en\u002F1.guide\u002F01.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Install the App","\u002Fen\u002Fguide\u002Finstallation","en\u002F1.guide\u002F02.installation","i-lucide-smartphone",{"title":21,"path":22,"stem":23,"icon":24},"Authentication","\u002Fen\u002Fguide\u002Fauth","en\u002F1.guide\u002F03.auth","i-lucide-lock",{"title":26,"path":27,"stem":28,"icon":29},"Wallets","\u002Fen\u002Fguide\u002Fwallets","en\u002F1.guide\u002F04.wallets","i-lucide-wallet",{"title":31,"path":32,"stem":33,"icon":34},"Categories","\u002Fen\u002Fguide\u002Fcategories","en\u002F1.guide\u002F05.categories","i-lucide-tags",{"title":36,"path":37,"stem":38,"icon":39},"Transactions","\u002Fen\u002Fguide\u002Ftransactions","en\u002F1.guide\u002F06.transactions","i-lucide-receipt",{"title":41,"path":42,"stem":43,"icon":44},"Transfers","\u002Fen\u002Fguide\u002Ftransfers","en\u002F1.guide\u002F07.transfers","i-lucide-arrow-left-right",{"title":46,"path":47,"stem":48,"icon":49},"Statistics","\u002Fen\u002Fguide\u002Fstatistics","en\u002F1.guide\u002F08.statistics","i-lucide-bar-chart-3",{"title":51,"path":52,"stem":53,"icon":54},"Theme","\u002Fen\u002Fguide\u002Ftheme","en\u002F1.guide\u002F09.theme","i-lucide-palette",{"title":56,"path":57,"stem":58,"icon":59},"Settings","\u002Fen\u002Fguide\u002Fsettings","en\u002F1.guide\u002F10.settings","i-lucide-settings",false,{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":60},"Development","i-lucide-code","\u002Fen\u002Fdevelopment","en\u002F2.development",[67,72,77,82,87,92,97,102,122],{"title":68,"path":69,"stem":70,"icon":71},"Installation","\u002Fen\u002Fdevelopment\u002Finstallation","en\u002F2.development\u002F01.installation","i-lucide-download",{"title":73,"path":74,"stem":75,"icon":76},"Codebase Graph","\u002Fen\u002Fdevelopment\u002Funderstand-anything","en\u002F2.development\u002F02.understand-anything","i-lucide-network",{"title":78,"path":79,"stem":80,"icon":81},"Offline & PWA","\u002Fen\u002Fdevelopment\u002Foffline","en\u002F2.development\u002F03.offline","i-lucide-wifi-off",{"title":83,"path":84,"stem":85,"icon":86},"Data Migration History","\u002Fen\u002Fdevelopment\u002Fmigration","en\u002F2.development\u002F04.migration","i-lucide-database",{"title":88,"path":89,"stem":90,"icon":91},"Deployment","\u002Fen\u002Fdevelopment\u002Fdeployment","en\u002F2.development\u002F05.deployment","i-lucide-rocket",{"title":93,"path":94,"stem":95,"icon":96},"Testing","\u002Fen\u002Fdevelopment\u002Ftesting","en\u002F2.development\u002F06.testing","i-lucide-flask-conical",{"title":98,"path":99,"stem":100,"icon":101},"Date Utilities","\u002Fen\u002Fdevelopment\u002Fdate-utilities","en\u002F2.development\u002F07.date-utilities","i-lucide-calendar",{"title":103,"path":104,"stem":105,"children":106,"page":60},"Ai Workflow","\u002Fen\u002Fdevelopment\u002Fai-workflow","en\u002F2.development\u002F08.ai-workflow",[107,112,117],{"title":108,"path":109,"stem":110,"icon":111},"Overview","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Foverview","en\u002F2.development\u002F08.ai-workflow\u002F01.overview","i-lucide-bot",{"title":113,"path":114,"stem":115,"icon":116},"Agents","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fagents","en\u002F2.development\u002F08.ai-workflow\u002F02.agents","i-lucide-users",{"title":118,"path":119,"stem":120,"icon":121},"Skills","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fskills","en\u002F2.development\u002F08.ai-workflow\u002F03.skills","i-lucide-lightbulb",{"title":123,"path":124,"stem":125,"icon":126},"Troubleshooting","\u002Fen\u002Fdevelopment\u002Ftroubleshooting","en\u002F2.development\u002F09.troubleshooting","i-lucide-life-buoy",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":60},"Reference","i-lucide-file-code","\u002Fen\u002Freference","en\u002F3.reference",[133,138,142,147,152,156,161,166],{"title":134,"path":135,"stem":136,"icon":137},"Architecture","\u002Fen\u002Freference\u002Farchitecture","en\u002F3.reference\u002F01.architecture","i-lucide-boxes",{"title":139,"path":140,"stem":141,"icon":44},"Transaction Types","\u002Fen\u002Freference\u002Ftransaction-types","en\u002F3.reference\u002F02.transaction-types",{"title":143,"path":144,"stem":145,"icon":146},"Sync","\u002Fen\u002Freference\u002Fsync","en\u002F3.reference\u002F03.sync","i-lucide-refresh-cw",{"title":148,"path":149,"stem":150,"icon":151},"Offline-first","\u002Fen\u002Freference\u002Foffline-first","en\u002F3.reference\u002F04.offline-first","i-lucide-list-ordered",{"title":153,"path":154,"stem":155,"icon":121},"Technical Decisions","\u002Fen\u002Freference\u002Ftech-decisions","en\u002F3.reference\u002F05.tech-decisions",{"title":157,"path":158,"stem":159,"icon":160},"Validation Strategy","\u002Fen\u002Freference\u002Fvalidation-strategy","en\u002F3.reference\u002F06.validation-strategy","i-lucide-shield-check",{"title":162,"path":163,"stem":164,"icon":165},"What Changed Since Firebase","\u002Fen\u002Freference\u002Ffirebase-migration","en\u002F3.reference\u002F07.firebase-migration","i-lucide-hamburger",{"title":167,"path":168,"stem":169,"icon":170},"Performance","\u002Fen\u002Freference\u002Fperformance","en\u002F3.reference\u002F08.performance","i-lucide-gauge",{"title":172,"icon":173,"path":174,"stem":175,"children":176,"page":60},"Premium","i-lucide-star","\u002Fen\u002Fpremium","en\u002F4.premium",[177,181,186],{"title":108,"path":178,"stem":179,"icon":180},"\u002Fen\u002Fpremium\u002Foverview","en\u002F4.premium\u002F01.overview","i-lucide-layers",{"title":182,"path":183,"stem":184,"icon":185},"Telegram Bot","\u002Fen\u002Fpremium\u002Ftelegram-bot","en\u002F4.premium\u002F02.telegram-bot","i-lucide-send",{"title":187,"path":188,"stem":189,"icon":190},"AI Chat","\u002Fen\u002Fpremium\u002Fai-chat","en\u002F4.premium\u002F03.ai-chat","i-lucide-sparkles",{"id":192,"title":93,"body":193,"description":614,"extension":615,"links":616,"meta":617,"navigation":618,"path":94,"seo":619,"stem":95,"__hash__":621},"docs_en\u002Fen\u002F2.development\u002F06.testing.md",{"type":194,"value":195,"toc":601},"minimark",[196,201,205,229,233,244,249,323,327,351,366,370,373,384,388,394,411,426,466,488,493,503,525,539,543,552,572,590,597],[197,198,200],"h2",{"id":199},"unit-tests","Unit Tests",[202,203,204],"p",{},"Run Vitest for unit tests:",[206,207,213],"pre",{"className":208,"code":209,"filename":210,"language":211,"meta":212,"style":212},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm test\n","Terminal","bash","",[214,215,216],"code",{"__ignoreMap":212},[217,218,221,225],"span",{"class":219,"line":220},"line",1,[217,222,224],{"class":223},"sBMFI","pnpm",[217,226,228],{"class":227},"sfazB"," test\n",[197,230,232],{"id":231},"e2e-tests","E2E Tests",[202,234,235,236,243],{},"E2E tests run with ",[237,238,242],"a",{"href":239,"rel":240},"https:\u002F\u002Fplaywright.dev",[241],"nofollow","Playwright",". Playwright ships browsers separately from its npm package, so a one-time install is required after cloning.",[245,246,248],"h3",{"id":247},"scripts","Scripts",[250,251,252,265],"table",{},[253,254,255],"thead",{},[256,257,258,262],"tr",{},[259,260,261],"th",{},"Script",[259,263,264],{},"Purpose",[266,267,268,283,293,303,313],"tbody",{},[256,269,270,276],{},[271,272,273],"td",{},[214,274,275],{},"pnpm test:e2e:install",[271,277,278,279,282],{},"One-time install of the Chromium binary. Re-run after upgrading ",[214,280,281],{},"@playwright\u002Ftest"," (browser version is pinned to the package version).",[256,284,285,290],{},[271,286,287],{},[214,288,289],{},"pnpm test:e2e",[271,291,292],{},"Run all tests headless. Use this in CI.",[256,294,295,300],{},[271,296,297],{},[214,298,299],{},"pnpm test:e2e:headed",[271,301,302],{},"Run tests with a visible browser window. Useful for watching a flow or debugging a flaky test.",[256,304,305,310],{},[271,306,307],{},[214,308,309],{},"pnpm test:e2e:ui",[271,311,312],{},"Open Playwright's interactive UI runner with time-travel debugging, watch mode, and step-by-step inspection. Best for writing new tests.",[256,314,315,320],{},[271,316,317],{},[214,318,319],{},"pnpm test:e2e:report",[271,321,322],{},"Open the HTML report from the last run (screenshots, traces, videos of failures).",[245,324,326],{"id":325},"typical-workflow","Typical Workflow",[328,329,330,336,341,346],"ol",{},[331,332,333,334],"li",{},"First time only: ",[214,335,275],{},[331,337,338,339],{},"Writing tests: ",[214,340,309],{},[331,342,343,344],{},"CI \u002F full sweep: ",[214,345,289],{},[331,347,348,349],{},"Investigating a failure: ",[214,350,319],{},[202,352,353,354,357,358,361,362,365],{},"Only ",[214,355,356],{},"test:e2e"," and ",[214,359,360],{},"test:e2e:install"," are strictly required - the rest are convenience wrappers around ",[214,363,364],{},"playwright"," CLI flags.",[197,367,369],{"id":368},"local-auth-without-google-seed-data","Local Auth Without Google (Seed Data)",[202,371,372],{},"Real-mode (PowerSync) testing needs a signed-in session, but Google OAuth can't be automated. The local stack ships a fixed email\u002Fpassword test user plus a demo-derived dataset, so agents \u002F Playwright \u002F manual runs can enter real mode without Google.",[374,375,376],"blockquote",{},[202,377,378,379,383],{},"Email\u002Fpassword is enabled in the Supabase ",[380,381,382],"strong",{},"backend"," but intentionally not exposed in the app UI (Google + Demo only). The seed user exists for local tests; its password is local-only and not a secret.",[245,385,387],{"id":386},"seed","Seed",[202,389,390,393],{},[214,391,392],{},"app\u002Fsupabase\u002Fseed.sql"," (generated, idempotent, transactional) creates:",[395,396,397,408],"ul",{},[331,398,399,400,403,404,407],{},"a confirmed test user - ",[214,401,402],{},"e2e@finapp.local"," \u002F ",[214,405,406],{},"finapp-e2e"," (fixed UUID), and",[331,409,410],{},"its demo dataset: 8 wallets, 32 categories, ~860 transactions.",[202,412,413,414,417,418,421,422,425],{},"It is applied automatically by ",[214,415,416],{},"supabase db reset"," (",[214,419,420],{},"[db.seed]"," in ",[214,423,424],{},"app\u002Fsupabase\u002Fconfig.toml","). To (re)apply against a running DB without a full reset:",[206,427,429],{"className":208,"code":428,"filename":210,"language":211,"meta":212,"style":212},"docker exec -i supabase_db_app psql -U postgres -d postgres \u003C app\u002Fsupabase\u002Fseed.sql\n",[214,430,431],{"__ignoreMap":212},[217,432,433,436,439,442,445,448,451,454,457,459,463],{"class":219,"line":220},[217,434,435],{"class":223},"docker",[217,437,438],{"class":227}," exec",[217,440,441],{"class":227}," -i",[217,443,444],{"class":227}," supabase_db_app",[217,446,447],{"class":227}," psql",[217,449,450],{"class":227}," -U",[217,452,453],{"class":227}," postgres",[217,455,456],{"class":227}," -d",[217,458,453],{"class":227},[217,460,462],{"class":461},"sMK4o"," \u003C",[217,464,465],{"class":227}," app\u002Fsupabase\u002Fseed.sql\n",[202,467,468,469,472,473,476,477,480,481,483,484,487],{},"The seed user's email\u002Fpassword works via the Supabase password grant even with email signup disabled (",[214,470,471],{},"[auth.email] enable_signup = false","), because the user is pre-created. The GoTrue token columns are seeded as ",[214,474,475],{},"''"," (not ",[214,478,479],{},"NULL",") - a ",[214,482,479],{}," triggers ",[214,485,486],{},"Database error querying schema"," on sign-in.",[489,490,492],"h4",{"id":491},"regenerating-the-seed-from-demo-data","Regenerating the seed from demo data",[202,494,495,498,499,502],{},[214,496,497],{},"seed.sql"," is produced from a snapshot of the real demo generator by ",[214,500,501],{},"app\u002Fscripts\u002Fgen-seed.mjs",":",[328,504,505,516],{},[331,506,507,508,511,512,515],{},"Open the app in demo mode, read the Pinia stores via devtools, and save ",[214,509,510],{},"{ wallets, categories, trns }"," to ",[214,513,514],{},"app\u002Fscripts\u002F.demo-snapshot.json"," (gitignored).",[331,517,518,521,522,524],{},[214,519,520],{},"node app\u002Fscripts\u002Fgen-seed.mjs"," - writes ",[214,523,392],{},".",[202,526,527,528,403,531,534,535,538],{},"Synthetic ",[214,529,530],{},"adjustment",[214,532,533],{},"transfer"," categories are skipped (they are ",[214,536,537],{},"trns.categoryId"," literals, not real category rows).",[245,540,542],{"id":541},"signing-in-as-the-test-user","Signing in as the test user",[202,544,545,548,549,502],{},[214,546,547],{},"app\u002Fscripts\u002Fdev-login.mjs"," mints a session via the password grant and prints the value for ",[214,550,551],{},"localStorage['finapp.auth']",[206,553,555],{"className":208,"code":554,"filename":210,"language":211,"meta":212,"style":212},"node app\u002Fscripts\u002Fdev-login.mjs --raw    # prints the raw session JSON\n",[214,556,557],{"__ignoreMap":212},[217,558,559,562,565,568],{"class":219,"line":220},[217,560,561],{"class":223},"node",[217,563,564],{"class":227}," app\u002Fscripts\u002Fdev-login.mjs",[217,566,567],{"class":227}," --raw",[217,569,571],{"class":570},"sHwdD","    # prints the raw session JSON\n",[202,573,574,575,578,579,582,583,403,586,589],{},"In the browser, set that value, clear the ",[214,576,577],{},"finapp.isDemo"," cookie, and reload - the app boots into real PowerSync mode as the test user and the seeded data streams in. Credentials\u002FURL are read from ",[214,580,581],{},"app\u002F.env"," (overridable via ",[214,584,585],{},"E2E_EMAIL",[214,587,588],{},"E2E_PASSWORD",").",[202,591,592,593,596],{},"This is also the basis for a Playwright ",[214,594,595],{},"storageState"," global-setup: sign in once via the grant, then reuse the session across tests.",[598,599,600],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":212,"searchDepth":602,"depth":602,"links":603},2,[604,605,610],{"id":199,"depth":602,"text":200},{"id":231,"depth":602,"text":232,"children":606},[607,609],{"id":247,"depth":608,"text":248},3,{"id":325,"depth":608,"text":326},{"id":368,"depth":602,"text":369,"children":611},[612,613],{"id":386,"depth":608,"text":387},{"id":541,"depth":608,"text":542},"Unit and E2E testing scripts.","md",null,{},{"icon":96},{"title":93,"description":620},"Unit tests with Vitest and E2E tests with Playwright.","ZwjbWwkMxlvREc-v4Z6Flhjy7EQLgxjJwcnQZO4Ydi8",[623,625],{"title":88,"path":89,"stem":90,"description":624,"icon":91,"children":-1},"Deploy Finapp and docs to Vercel from a monorepo, with Supabase and PowerSync as the backend.",{"title":98,"path":99,"stem":100,"description":626,"icon":101,"children":-1},"Date range computation, period formatting, and interval bucketing.",1782114343806]