From e6f17f30a1ca1d90c719f1f33d6c7678c1af4794 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:00:15 +0900 Subject: [PATCH 01/12] =?UTF-8?q?chore(admin):=20playwright=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20=EB=B0=8F=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pnp.cjs | 39 +++++++++++++++++++++++ apps/admin/package.json | 5 ++- apps/admin/playwright.config.ts | 28 +++++++++++++++++ apps/admin/vite.config.ts | 5 ++- yarn.lock | 55 +++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 apps/admin/playwright.config.ts diff --git a/.pnp.cjs b/.pnp.cjs index 61824c15..b27df9fa 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -4714,6 +4714,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@playwright/test", [\ + ["npm:1.59.1", {\ + "packageLocation": "./.yarn/cache/@playwright-test-npm-1.59.1-8a59644a90-8c2d94a860.zip/node_modules/@playwright/test/",\ + "packageDependencies": [\ + ["@playwright/test", "npm:1.59.1"],\ + ["playwright", "npm:1.59.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@radix-ui/number", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/@radix-ui-number-npm-1.0.1-167c973d35-42e4870cd1.zip/node_modules/@radix-ui/number/",\ @@ -9566,6 +9576,7 @@ const RAW_RUNTIME_STATE = ["@emotion/babel-plugin", "npm:11.11.0"],\ ["@emotion/react", "virtual:de80dc576383b2386358abc0e9fe49c00e3397fe355a0337462b73ab3115c2e557eb85784ee0fe776394cc11dd020b4e84dbbd75acf72ee6d54415d82d21f5c5#npm:11.11.3"],\ ["@emotion/styled", "virtual:85869d3eba7afdb6f94c001c9503942ddc4354e881daf63c24e9d58366ea9f25c6bac2df65ae0f5266c54cd36fe68f0d9568da3a1ab62446405c98ac852f4431#npm:11.11.0"],\ + ["@playwright/test", "npm:1.59.1"],\ ["@react-pdf/renderer", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:3.4.4"],\ ["@tanstack/react-table", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:8.12.0"],\ ["@testing-library/dom", "npm:10.4.1"],\ @@ -13121,6 +13132,14 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["fsevents", [\ + ["patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1", {\ + "packageLocation": "./.yarn/unplugged/fsevents-patch-19706e7e35/node_modules/fsevents/",\ + "packageDependencies": [\ + ["fsevents", "patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1"],\ + ["node-gyp", "npm:10.0.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1", {\ "packageLocation": "./.yarn/unplugged/fsevents-patch-6b67494872/node_modules/fsevents/",\ "packageDependencies": [\ @@ -16928,6 +16947,26 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["playwright", [\ + ["npm:1.59.1", {\ + "packageLocation": "./.yarn/cache/playwright-npm-1.59.1-8e8808a3f1-dfe38396e6.zip/node_modules/playwright/",\ + "packageDependencies": [\ + ["playwright", "npm:1.59.1"],\ + ["fsevents", "patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1"],\ + ["playwright-core", "npm:1.59.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["playwright-core", [\ + ["npm:1.59.1", {\ + "packageLocation": "./.yarn/cache/playwright-core-npm-1.59.1-e63605649f-d41a74d968.zip/node_modules/playwright-core/",\ + "packageDependencies": [\ + ["playwright-core", "npm:1.59.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["polished", [\ ["npm:4.3.1", {\ "packageLocation": "./.yarn/cache/polished-npm-4.3.1-96b1782f82-45480d4c72.zip/node_modules/polished/",\ diff --git a/apps/admin/package.json b/apps/admin/package.json index 3f038c3e..a795a9e4 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -11,7 +11,9 @@ "type-check": "tsc --noEmit", "preview": "vite preview", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "test:e2e": "playwright test -c playwright.config.ts", + "test:e2e:ui": "playwright test -c playwright.config.ts --ui" }, "dependencies": { "@boolti/api": "*", @@ -56,6 +58,7 @@ "@boolti/eslint-config": "*", "@boolti/typescript-config": "*", "@emotion/babel-plugin": "^11.11.0", + "@playwright/test": "^1.54.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.0.1", "@types/js-cookie": "^3.0.6", diff --git a/apps/admin/playwright.config.ts b/apps/admin/playwright.config.ts new file mode 100644 index 00000000..0c9f8d91 --- /dev/null +++ b/apps/admin/playwright.config.ts @@ -0,0 +1,28 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e/functional', + timeout: 30_000, + use: { + baseURL: 'https://127.0.0.1:4173', + ignoreHTTPSErrors: true, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + }, + webServer: { + command: 'VITE_BASE_API_URL=https://127.0.0.1:4173/ yarn workspace admin dev --host 127.0.0.1 --port 4173', + url: 'https://127.0.0.1:4173', + ignoreHTTPSErrors: true, + reuseExistingServer: true, + timeout: 120_000, + }, + projects: [ + { + name: 'desktop', + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1280, height: 800 }, + }, + }, + ], +}); diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts index 5438f61e..b760fb9b 100644 --- a/apps/admin/vite.config.ts +++ b/apps/admin/vite.config.ts @@ -1,8 +1,11 @@ import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vitest/config'; // https://vitejs.dev/config/ export default defineConfig({ + test: { + exclude: ['e2e/**'], + }, resolve: { alias: [{ find: '~', replacement: '/src' }], }, diff --git a/yarn.lock b/yarn.lock index 503f5a62..56c2d790 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3052,6 +3052,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.54.1": + version: 1.59.1 + resolution: "@playwright/test@npm:1.59.1" + dependencies: + playwright: "npm:1.59.1" + bin: + playwright: cli.js + checksum: 10c0/8c2d94a860d3c254a0b114df2f888ad0a0e9310f45b6059bd5d4da196d965cadf6922267cef0881cfa9784d4bef6d78363d2c2d94caa64be67ff644c41162137 + languageName: node + linkType: hard + "@radix-ui/number@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/number@npm:1.0.1" @@ -6195,6 +6206,7 @@ __metadata: "@emotion/babel-plugin": "npm:^11.11.0" "@emotion/react": "npm:^11.11.3" "@emotion/styled": "npm:^11.11.0" + "@playwright/test": "npm:^1.54.1" "@react-pdf/renderer": "npm:^3.4.4" "@tanstack/react-table": "npm:^8.12.0" "@testing-library/dom": "npm:^10.4.0" @@ -9318,6 +9330,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -9328,6 +9350,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -12664,6 +12695,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.59.1": + version: 1.59.1 + resolution: "playwright-core@npm:1.59.1" + bin: + playwright-core: cli.js + checksum: 10c0/d41a74d9681ce3beb3d5239e9ed577710b4ad099a6ca2476219c6599d51e9cb4b80bd72ed82c528da6a5d929c18ae3b872cf02bb83f78fa1c2cb9199c501abee + languageName: node + linkType: hard + +"playwright@npm:1.59.1": + version: 1.59.1 + resolution: "playwright@npm:1.59.1" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.59.1" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10c0/dfe38396e616e5c4f98825ce90037bb96e477c5a2bd9258a24854f8ce72a8a41427b19098863866f85aa0216e70287dd537c4438d761aca93995e31ae099c533 + languageName: node + linkType: hard + "polished@npm:^4.2.2": version: 4.3.1 resolution: "polished@npm:4.3.1" From d0ea5283e58f45a0957972b7d50ac9a4ac881245 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:20:19 +0900 Subject: [PATCH 02/12] =?UTF-8?q?chore(admin):=20msw=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pnp.cjs | 444 ++++++++++++++++++++++++++++++++ apps/admin/package.json | 8 +- apps/admin/playwright.config.ts | 8 +- apps/admin/vite.config.ts | 14 +- yarn.lock | 374 ++++++++++++++++++++++++++- 5 files changed, 836 insertions(+), 12 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index b27df9fa..46af3271 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -4311,6 +4311,93 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@inquirer/ansi", [\ + ["npm:2.0.5", {\ + "packageLocation": "./.yarn/cache/@inquirer-ansi-npm-2.0.5-8af5e6ad44-ad61532e5b.zip/node_modules/@inquirer/ansi/",\ + "packageDependencies": [\ + ["@inquirer/ansi", "npm:2.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/confirm", [\ + ["npm:6.0.12", {\ + "packageLocation": "./.yarn/cache/@inquirer-confirm-npm-6.0.12-6d7d2fbcfc-36e9b1ef60.zip/node_modules/@inquirer/confirm/",\ + "packageDependencies": [\ + ["@inquirer/confirm", "npm:6.0.12"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:7e2aa39288a9bb75e6200124f1541c326c6028243dab00aa6c226e8828b558fe1489c4fd366fd37df54589d43d1f0c314c8cc2e624701deeaf35e2766fd42c47#npm:6.0.12", {\ + "packageLocation": "./.yarn/__virtual__/@inquirer-confirm-virtual-e16cf0e037/0/cache/@inquirer-confirm-npm-6.0.12-6d7d2fbcfc-36e9b1ef60.zip/node_modules/@inquirer/confirm/",\ + "packageDependencies": [\ + ["@inquirer/confirm", "virtual:7e2aa39288a9bb75e6200124f1541c326c6028243dab00aa6c226e8828b558fe1489c4fd366fd37df54589d43d1f0c314c8cc2e624701deeaf35e2766fd42c47#npm:6.0.12"],\ + ["@inquirer/core", "virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:11.1.9"],\ + ["@inquirer/type", "virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:4.0.5"],\ + ["@types/node", null]\ + ],\ + "packagePeers": [\ + "@types/node"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/core", [\ + ["npm:11.1.9", {\ + "packageLocation": "./.yarn/cache/@inquirer-core-npm-11.1.9-6370fda053-f7b162ce5f.zip/node_modules/@inquirer/core/",\ + "packageDependencies": [\ + ["@inquirer/core", "npm:11.1.9"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:11.1.9", {\ + "packageLocation": "./.yarn/__virtual__/@inquirer-core-virtual-c4f6012aa9/0/cache/@inquirer-core-npm-11.1.9-6370fda053-f7b162ce5f.zip/node_modules/@inquirer/core/",\ + "packageDependencies": [\ + ["@inquirer/core", "virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:11.1.9"],\ + ["@inquirer/ansi", "npm:2.0.5"],\ + ["@inquirer/figures", "npm:2.0.5"],\ + ["@inquirer/type", "virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:4.0.5"],\ + ["@types/node", null],\ + ["cli-width", "npm:4.1.0"],\ + ["fast-wrap-ansi", "npm:0.2.0"],\ + ["mute-stream", "npm:3.0.0"],\ + ["signal-exit", "npm:4.1.0"]\ + ],\ + "packagePeers": [\ + "@types/node"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/figures", [\ + ["npm:2.0.5", {\ + "packageLocation": "./.yarn/cache/@inquirer-figures-npm-2.0.5-6cb995d689-139671b88f.zip/node_modules/@inquirer/figures/",\ + "packageDependencies": [\ + ["@inquirer/figures", "npm:2.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/type", [\ + ["npm:4.0.5", {\ + "packageLocation": "./.yarn/cache/@inquirer-type-npm-4.0.5-c68ee3257f-390edb0fd1.zip/node_modules/@inquirer/type/",\ + "packageDependencies": [\ + ["@inquirer/type", "npm:4.0.5"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:4.0.5", {\ + "packageLocation": "./.yarn/__virtual__/@inquirer-type-virtual-e0de942d8b/0/cache/@inquirer-type-npm-4.0.5-c68ee3257f-390edb0fd1.zip/node_modules/@inquirer/type/",\ + "packageDependencies": [\ + ["@inquirer/type", "virtual:e16cf0e037e9dd550d338c6cad8ae802d855d5a39c64b1ef1a16eb54637cba28b15da825e30e4681240e412130a394c51848193b2f43a6d64a0eb127d1147206#npm:4.0.5"],\ + ["@types/node", null]\ + ],\ + "packagePeers": [\ + "@types/node"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@isaacs/cliui", [\ ["npm:8.0.2", {\ "packageLocation": "./.yarn/cache/@isaacs-cliui-npm-8.0.2-f4364666d5-b1bf42535d.zip/node_modules/@isaacs/cliui/",\ @@ -4629,6 +4716,21 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@mswjs/interceptors", [\ + ["npm:0.41.8", {\ + "packageLocation": "./.yarn/cache/@mswjs-interceptors-npm-0.41.8-4b2fae0579-6ffe97fc7a.zip/node_modules/@mswjs/interceptors/",\ + "packageDependencies": [\ + ["@mswjs/interceptors", "npm:0.41.8"],\ + ["@open-draft/deferred-promise", "npm:2.2.0"],\ + ["@open-draft/logger", "npm:0.3.0"],\ + ["@open-draft/until", "npm:2.1.0"],\ + ["is-node-process", "npm:1.2.0"],\ + ["outvariant", "npm:1.4.3"],\ + ["strict-event-emitter", "npm:0.5.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@ndelangen/get-tarball", [\ ["npm:3.0.9", {\ "packageLocation": "./.yarn/cache/@ndelangen-get-tarball-npm-3.0.9-c4692f22a4-d66e76c6c9.zip/node_modules/@ndelangen/get-tarball/",\ @@ -4696,6 +4798,42 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@open-draft/deferred-promise", [\ + ["npm:2.2.0", {\ + "packageLocation": "./.yarn/cache/@open-draft-deferred-promise-npm-2.2.0-adf396dc9f-eafc1b1d0f.zip/node_modules/@open-draft/deferred-promise/",\ + "packageDependencies": [\ + ["@open-draft/deferred-promise", "npm:2.2.0"]\ + ],\ + "linkType": "HARD"\ + }],\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/@open-draft-deferred-promise-npm-3.0.0-f8ff16b581-4dd697e554.zip/node_modules/@open-draft/deferred-promise/",\ + "packageDependencies": [\ + ["@open-draft/deferred-promise", "npm:3.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@open-draft/logger", [\ + ["npm:0.3.0", {\ + "packageLocation": "./.yarn/cache/@open-draft-logger-npm-0.3.0-12b03e55aa-90010647b2.zip/node_modules/@open-draft/logger/",\ + "packageDependencies": [\ + ["@open-draft/logger", "npm:0.3.0"],\ + ["is-node-process", "npm:1.2.0"],\ + ["outvariant", "npm:1.4.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@open-draft/until", [\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/@open-draft-until-npm-2.1.0-e27da33c52-61d3f99718.zip/node_modules/@open-draft/until/",\ + "packageDependencies": [\ + ["@open-draft/until", "npm:2.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@pkgjs/parseargs", [\ ["npm:0.11.0", {\ "packageLocation": "./.yarn/cache/@pkgjs-parseargs-npm-0.11.0-cd2a3fe948-5bd7576bb1.zip/node_modules/@pkgjs/parseargs/",\ @@ -8858,6 +8996,25 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@types/set-cookie-parser", [\ + ["npm:2.4.10", {\ + "packageLocation": "./.yarn/cache/@types-set-cookie-parser-npm-2.4.10-d2555d8830-010b0c582e.zip/node_modules/@types/set-cookie-parser/",\ + "packageDependencies": [\ + ["@types/set-cookie-parser", "npm:2.4.10"],\ + ["@types/node", "npm:20.11.16"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@types/statuses", [\ + ["npm:2.0.6", {\ + "packageLocation": "./.yarn/cache/@types-statuses-npm-2.0.6-ef1f12f3e4-dd88c220b0.zip/node_modules/@types/statuses/",\ + "packageDependencies": [\ + ["@types/statuses", "npm:2.0.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/unist", [\ ["npm:2.0.10", {\ "packageLocation": "./.yarn/cache/@types-unist-npm-2.0.10-f9b9ac478e-5f247dc222.zip/node_modules/@types/unist/",\ @@ -9594,6 +9751,7 @@ const RAW_RUNTIME_STATE = ["jsdom", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:26.1.0"],\ ["jwt-decode", "npm:4.0.0"],\ ["lodash.debounce", "npm:4.0.8"],\ + ["msw", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:2.14.2"],\ ["qrcode.react", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:3.1.0"],\ ["quill", "npm:2.0.3"],\ ["react", "npm:18.2.0"],\ @@ -10783,6 +10941,27 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["cli-width", [\ + ["npm:4.1.0", {\ + "packageLocation": "./.yarn/cache/cli-width-npm-4.1.0-c08b53be83-1fbd564135.zip/node_modules/cli-width/",\ + "packageDependencies": [\ + ["cli-width", "npm:4.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["cliui", [\ + ["npm:8.0.1", {\ + "packageLocation": "./.yarn/cache/cliui-npm-8.0.1-3b029092cf-4bda0f09c3.zip/node_modules/cliui/",\ + "packageDependencies": [\ + ["cliui", "npm:8.0.1"],\ + ["string-width", "npm:4.2.3"],\ + ["strip-ansi", "npm:6.0.1"],\ + ["wrap-ansi", "npm:7.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["clone", [\ ["npm:1.0.4", {\ "packageLocation": "./.yarn/cache/clone-npm-1.0.4-a610fcbcf9-2176952b36.zip/node_modules/clone/",\ @@ -11028,6 +11207,13 @@ const RAW_RUNTIME_STATE = ["cookie", "npm:0.5.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/cookie-npm-1.1.1-881103ddeb-79c4ddc0fc.zip/node_modules/cookie/",\ + "packageDependencies": [\ + ["cookie", "npm:1.1.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["cookie-signature", [\ @@ -12769,6 +12955,35 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["fast-string-truncated-width", [\ + ["npm:3.0.3", {\ + "packageLocation": "./.yarn/cache/fast-string-truncated-width-npm-3.0.3-1f742a6cd9-043b866339.zip/node_modules/fast-string-truncated-width/",\ + "packageDependencies": [\ + ["fast-string-truncated-width", "npm:3.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["fast-string-width", [\ + ["npm:3.0.2", {\ + "packageLocation": "./.yarn/cache/fast-string-width-npm-3.0.2-f4e7c6c8bc-c8822d1753.zip/node_modules/fast-string-width/",\ + "packageDependencies": [\ + ["fast-string-width", "npm:3.0.2"],\ + ["fast-string-truncated-width", "npm:3.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["fast-wrap-ansi", [\ + ["npm:0.2.0", {\ + "packageLocation": "./.yarn/cache/fast-wrap-ansi-npm-0.2.0-11e4628564-c0eb6debee.zip/node_modules/fast-wrap-ansi/",\ + "packageDependencies": [\ + ["fast-wrap-ansi", "npm:0.2.0"],\ + ["fast-string-width", "npm:3.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["fastest-stable-stringify", [\ ["npm:2.0.2", {\ "packageLocation": "./.yarn/cache/fastest-stable-stringify-npm-2.0.2-f2a059d214-abbe5ff48f.zip/node_modules/fastest-stable-stringify/",\ @@ -13207,6 +13422,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["get-caller-file", [\ + ["npm:2.0.5", {\ + "packageLocation": "./.yarn/cache/get-caller-file-npm-2.0.5-80e8a86305-c6c7b60271.zip/node_modules/get-caller-file/",\ + "packageDependencies": [\ + ["get-caller-file", "npm:2.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["get-func-name", [\ ["npm:2.0.2", {\ "packageLocation": "./.yarn/cache/get-func-name-npm-2.0.2-409dbe3703-89830fd076.zip/node_modules/get-func-name/",\ @@ -13485,6 +13709,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["graphql", [\ + ["npm:16.13.2", {\ + "packageLocation": "./.yarn/cache/graphql-npm-16.13.2-dd8254da13-64e822a0a0.zip/node_modules/graphql/",\ + "packageDependencies": [\ + ["graphql", "npm:16.13.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["gunzip-maybe", [\ ["npm:1.4.2", {\ "packageLocation": "./.yarn/cache/gunzip-maybe-npm-1.4.2-97df376cb9-42798a8061.zip/node_modules/gunzip-maybe/",\ @@ -13630,6 +13863,17 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["headers-polyfill", [\ + ["npm:5.0.1", {\ + "packageLocation": "./.yarn/cache/headers-polyfill-npm-5.0.1-5c80cb3378-c269730a88.zip/node_modules/headers-polyfill/",\ + "packageDependencies": [\ + ["headers-polyfill", "npm:5.0.1"],\ + ["@types/set-cookie-parser", "npm:2.4.10"],\ + ["set-cookie-parser", "npm:3.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["hoist-non-react-statics", [\ ["npm:3.3.2", {\ "packageLocation": "./.yarn/cache/hoist-non-react-statics-npm-3.3.2-e7b709e6c1-fe0889169e.zip/node_modules/hoist-non-react-statics/",\ @@ -14255,6 +14499,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["is-node-process", [\ + ["npm:1.2.0", {\ + "packageLocation": "./.yarn/cache/is-node-process-npm-1.2.0-34f2abe8e1-5b24fda677.zip/node_modules/is-node-process/",\ + "packageDependencies": [\ + ["is-node-process", "npm:1.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["is-number", [\ ["npm:7.0.0", {\ "packageLocation": "./.yarn/cache/is-number-npm-7.0.0-060086935c-b4686d0d30.zip/node_modules/is-number/",\ @@ -16104,6 +16357,46 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["msw", [\ + ["npm:2.14.2", {\ + "packageLocation": "./.yarn/unplugged/msw-virtual-7e2aa39288/node_modules/msw/",\ + "packageDependencies": [\ + ["msw", "npm:2.14.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:2.14.2", {\ + "packageLocation": "./.yarn/unplugged/msw-virtual-7e2aa39288/node_modules/msw/",\ + "packageDependencies": [\ + ["msw", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:2.14.2"],\ + ["@inquirer/confirm", "virtual:7e2aa39288a9bb75e6200124f1541c326c6028243dab00aa6c226e8828b558fe1489c4fd366fd37df54589d43d1f0c314c8cc2e624701deeaf35e2766fd42c47#npm:6.0.12"],\ + ["@mswjs/interceptors", "npm:0.41.8"],\ + ["@open-draft/deferred-promise", "npm:3.0.0"],\ + ["@types/statuses", "npm:2.0.6"],\ + ["@types/typescript", null],\ + ["cookie", "npm:1.1.1"],\ + ["graphql", "npm:16.13.2"],\ + ["headers-polyfill", "npm:5.0.1"],\ + ["is-node-process", "npm:1.2.0"],\ + ["outvariant", "npm:1.4.3"],\ + ["path-to-regexp", "npm:6.3.0"],\ + ["picocolors", "npm:1.1.1"],\ + ["rettime", "npm:0.11.11"],\ + ["statuses", "npm:2.0.2"],\ + ["strict-event-emitter", "npm:0.5.1"],\ + ["tough-cookie", "npm:6.0.1"],\ + ["type-fest", "npm:5.6.0"],\ + ["typescript", "patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7"],\ + ["until-async", "npm:3.0.2"],\ + ["yargs", "npm:17.7.2"]\ + ],\ + "packagePeers": [\ + "@types/typescript",\ + "typescript"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["mutation-observer", [\ ["npm:1.0.3", {\ "packageLocation": "./.yarn/cache/mutation-observer-npm-1.0.3-fa3b236d74-2f010fdec4.zip/node_modules/mutation-observer/",\ @@ -16113,6 +16406,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["mute-stream", [\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/mute-stream-npm-3.0.0-b51769acce-12cdb36a10.zip/node_modules/mute-stream/",\ + "packageDependencies": [\ + ["mute-stream", "npm:3.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["nan", [\ ["npm:2.20.0", {\ "packageLocation": "./.yarn/unplugged/nan-npm-2.20.0-5b5be83e88/node_modules/nan/",\ @@ -16578,6 +16880,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["outvariant", [\ + ["npm:1.4.3", {\ + "packageLocation": "./.yarn/cache/outvariant-npm-1.4.3-192f951f81-5976ca7740.zip/node_modules/outvariant/",\ + "packageDependencies": [\ + ["outvariant", "npm:1.4.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["p-limit", [\ ["npm:2.3.0", {\ "packageLocation": "./.yarn/cache/p-limit-npm-2.3.0-94a0310039-8da01ac53e.zip/node_modules/p-limit/",\ @@ -16801,6 +17112,13 @@ const RAW_RUNTIME_STATE = ["path-to-regexp", "npm:0.1.7"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:6.3.0", {\ + "packageLocation": "./.yarn/cache/path-to-regexp-npm-6.3.0-ee2cdde576-73b67f4638.zip/node_modules/path-to-regexp/",\ + "packageDependencies": [\ + ["path-to-regexp", "npm:6.3.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["path-type", [\ @@ -19724,6 +20042,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["require-directory", [\ + ["npm:2.1.1", {\ + "packageLocation": "./.yarn/cache/require-directory-npm-2.1.1-8608aee50b-83aa76a7bc.zip/node_modules/require-directory/",\ + "packageDependencies": [\ + ["require-directory", "npm:2.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["require-from-string", [\ ["npm:2.0.2", {\ "packageLocation": "./.yarn/cache/require-from-string-npm-2.0.2-8557e0db12-aaa267e0c5.zip/node_modules/require-from-string/",\ @@ -19799,6 +20126,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["rettime", [\ + ["npm:0.11.11", {\ + "packageLocation": "./.yarn/cache/rettime-npm-0.11.11-a494d8bd51-021fc9d987.zip/node_modules/rettime/",\ + "packageDependencies": [\ + ["rettime", "npm:0.11.11"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["reusify", [\ ["npm:1.0.4", {\ "packageLocation": "./.yarn/cache/reusify-npm-1.0.4-95ac4aec11-c19ef26e4e.zip/node_modules/reusify/",\ @@ -20095,6 +20431,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["set-cookie-parser", [\ + ["npm:3.1.0", {\ + "packageLocation": "./.yarn/cache/set-cookie-parser-npm-3.1.0-f66fecf7c7-7465e389ff.zip/node_modules/set-cookie-parser/",\ + "packageDependencies": [\ + ["set-cookie-parser", "npm:3.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["set-function-length", [\ ["npm:1.2.0", {\ "packageLocation": "./.yarn/cache/set-function-length-npm-1.2.0-aac8e100c3-b4fdf68bbf.zip/node_modules/set-function-length/",\ @@ -20478,6 +20823,13 @@ const RAW_RUNTIME_STATE = ["statuses", "npm:2.0.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.0.2", {\ + "packageLocation": "./.yarn/cache/statuses-npm-2.0.2-2d84c63b8c-a9947d98ad.zip/node_modules/statuses/",\ + "packageDependencies": [\ + ["statuses", "npm:2.0.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["std-env", [\ @@ -20561,6 +20913,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["strict-event-emitter", [\ + ["npm:0.5.1", {\ + "packageLocation": "./.yarn/cache/strict-event-emitter-npm-0.5.1-8414bf36b3-f5228a6e6b.zip/node_modules/strict-event-emitter/",\ + "packageDependencies": [\ + ["strict-event-emitter", "npm:0.5.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["string-convert", [\ ["npm:0.2.1", {\ "packageLocation": "./.yarn/cache/string-convert-npm-0.2.1-f1a42cb9ea-00673ed8a3.zip/node_modules/string-convert/",\ @@ -20963,6 +21324,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["tagged-tag", [\ + ["npm:1.0.0", {\ + "packageLocation": "./.yarn/cache/tagged-tag-npm-1.0.0-80e0c0061d-91d25c9ffb.zip/node_modules/tagged-tag/",\ + "packageDependencies": [\ + ["tagged-tag", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["tar", [\ ["npm:6.2.0", {\ "packageLocation": "./.yarn/cache/tar-npm-6.2.0-3eb25205a7-02ca064a1a.zip/node_modules/tar/",\ @@ -21190,6 +21560,14 @@ const RAW_RUNTIME_STATE = ["tldts-core", "npm:6.1.86"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.0.30", {\ + "packageLocation": "./.yarn/cache/tldts-npm-7.0.30-e815b3f846-c36f7b480f.zip/node_modules/tldts/",\ + "packageDependencies": [\ + ["tldts", "npm:7.0.30"],\ + ["tldts-core", "npm:7.0.30"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["tldts-core", [\ @@ -21199,6 +21577,13 @@ const RAW_RUNTIME_STATE = ["tldts-core", "npm:6.1.86"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.0.30", {\ + "packageLocation": "./.yarn/cache/tldts-core-npm-7.0.30-ed52aa351a-e3cd730e96.zip/node_modules/tldts-core/",\ + "packageDependencies": [\ + ["tldts-core", "npm:7.0.30"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["tmpl", [\ @@ -21264,6 +21649,14 @@ const RAW_RUNTIME_STATE = ["tldts", "npm:6.1.86"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:6.0.1", {\ + "packageLocation": "./.yarn/cache/tough-cookie-npm-6.0.1-7a26930694-ec70bd6b12.zip/node_modules/tough-cookie/",\ + "packageDependencies": [\ + ["tough-cookie", "npm:6.0.1"],\ + ["tldts", "npm:7.0.30"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["tr46", [\ @@ -21515,6 +21908,14 @@ const RAW_RUNTIME_STATE = ["type-fest", "npm:2.19.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:5.6.0", {\ + "packageLocation": "./.yarn/cache/type-fest-npm-5.6.0-daf055db8b-5468a8ffda.zip/node_modules/type-fest/",\ + "packageDependencies": [\ + ["type-fest", "npm:5.6.0"],\ + ["tagged-tag", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["type-is", [\ @@ -21853,6 +22254,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["until-async", [\ + ["npm:3.0.2", {\ + "packageLocation": "./.yarn/cache/until-async-npm-3.0.2-1afeb8e787-61c8b03895.zip/node_modules/until-async/",\ + "packageDependencies": [\ + ["until-async", "npm:3.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["untildify", [\ ["npm:4.0.0", {\ "packageLocation": "./.yarn/cache/untildify-npm-4.0.0-4a8b569825-d758e624c7.zip/node_modules/untildify/",\ @@ -22733,6 +23143,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["y18n", [\ + ["npm:5.0.8", {\ + "packageLocation": "./.yarn/cache/y18n-npm-5.0.8-5f3a0a7e62-4df2842c36.zip/node_modules/y18n/",\ + "packageDependencies": [\ + ["y18n", "npm:5.0.8"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["yallist", [\ ["npm:3.1.1", {\ "packageLocation": "./.yarn/cache/yallist-npm-3.1.1-a568a556b4-c66a5c46bc.zip/node_modules/yallist/",\ @@ -22758,6 +23177,31 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["yargs", [\ + ["npm:17.7.2", {\ + "packageLocation": "./.yarn/cache/yargs-npm-17.7.2-80b62638e1-ccd7e723e6.zip/node_modules/yargs/",\ + "packageDependencies": [\ + ["yargs", "npm:17.7.2"],\ + ["cliui", "npm:8.0.1"],\ + ["escalade", "npm:3.1.1"],\ + ["get-caller-file", "npm:2.0.5"],\ + ["require-directory", "npm:2.1.1"],\ + ["string-width", "npm:4.2.3"],\ + ["y18n", "npm:5.0.8"],\ + ["yargs-parser", "npm:21.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["yargs-parser", [\ + ["npm:21.1.1", {\ + "packageLocation": "./.yarn/cache/yargs-parser-npm-21.1.1-8fdc003314-f84b5e4816.zip/node_modules/yargs-parser/",\ + "packageDependencies": [\ + ["yargs-parser", "npm:21.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["yauzl", [\ ["npm:2.10.0", {\ "packageLocation": "./.yarn/cache/yauzl-npm-2.10.0-72e70ea021-f265002af7.zip/node_modules/yauzl/",\ diff --git a/apps/admin/package.json b/apps/admin/package.json index a795a9e4..b0ce479e 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -12,7 +12,7 @@ "preview": "vite preview", "test": "vitest run", "test:watch": "vitest", - "test:e2e": "playwright test -c playwright.config.ts", + "test:e2e:functional": "playwright test -c playwright.config.ts", "test:e2e:ui": "playwright test -c playwright.config.ts --ui" }, "dependencies": { @@ -67,8 +67,14 @@ "@types/react-dom": "^18.2.17", "@vitejs/plugin-react": "^4.2.1", "jsdom": "^26.1.0", + "msw": "^2.14.2", "typescript": "^5.2.2", "vite": "^5.0.8", "vitest": "^2.1.9" + }, + "msw": { + "workerDirectory": [ + "public" + ] } } diff --git a/apps/admin/playwright.config.ts b/apps/admin/playwright.config.ts index 0c9f8d91..fb0ecae7 100644 --- a/apps/admin/playwright.config.ts +++ b/apps/admin/playwright.config.ts @@ -4,15 +4,13 @@ export default defineConfig({ testDir: './e2e/functional', timeout: 30_000, use: { - baseURL: 'https://127.0.0.1:4173', - ignoreHTTPSErrors: true, + baseURL: 'http://127.0.0.1:4173', trace: 'retain-on-failure', screenshot: 'only-on-failure', }, webServer: { - command: 'VITE_BASE_API_URL=https://127.0.0.1:4173/ yarn workspace admin dev --host 127.0.0.1 --port 4173', - url: 'https://127.0.0.1:4173', - ignoreHTTPSErrors: true, + command: 'VITE_E2E_MSW=true VITE_BASE_API_URL=http://127.0.0.1:4173/ yarn workspace admin dev --host 127.0.0.1 --port 4173', + url: 'http://127.0.0.1:4173', reuseExistingServer: true, timeout: 120_000, }, diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts index b760fb9b..42440047 100644 --- a/apps/admin/vite.config.ts +++ b/apps/admin/vite.config.ts @@ -1,6 +1,8 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vitest/config'; +const isE2EMsw = process.env.VITE_E2E_MSW === 'true'; + // https://vitejs.dev/config/ export default defineConfig({ test: { @@ -12,11 +14,13 @@ export default defineConfig({ server: { port: 8080, cors: false, - host: 'dev.boolti.in', - https: { - key: './dev.boolti.in-key.pem', - cert: './dev.boolti.in.pem', - }, + host: isE2EMsw ? '127.0.0.1' : 'dev.boolti.in', + https: isE2EMsw + ? undefined + : { + key: './dev.boolti.in-key.pem', + cert: './dev.boolti.in.pem', + }, }, plugins: [ react({ diff --git a/yarn.lock b/yarn.lock index 56c2d790..b56913fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2728,6 +2728,67 @@ __metadata: languageName: node linkType: hard +"@inquirer/ansi@npm:^2.0.5": + version: 2.0.5 + resolution: "@inquirer/ansi@npm:2.0.5" + checksum: 10c0/ad61532e5bb47473e3d987c32d4015499a8ce5f4f86e46467e8e672fc52670beb303905d6b324e453935a61671f59f3b9b1b6a1edbbe1f64085e2bb87735e295 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^6.0.11": + version: 6.0.12 + resolution: "@inquirer/confirm@npm:6.0.12" + dependencies: + "@inquirer/core": "npm:^11.1.9" + "@inquirer/type": "npm:^4.0.5" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/36e9b1ef60e08562f07bcbcd78ba0a681b2fa3e5f54fa5e12303fc5982e8ba875ed782c24161e2295028b2b404ba690e841837303d16eeeb28df7aa9eb8aa835 + languageName: node + linkType: hard + +"@inquirer/core@npm:^11.1.9": + version: 11.1.9 + resolution: "@inquirer/core@npm:11.1.9" + dependencies: + "@inquirer/ansi": "npm:^2.0.5" + "@inquirer/figures": "npm:^2.0.5" + "@inquirer/type": "npm:^4.0.5" + cli-width: "npm:^4.1.0" + fast-wrap-ansi: "npm:^0.2.0" + mute-stream: "npm:^3.0.0" + signal-exit: "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/f7b162ce5f67fb75aab00a3668fdd8c8629aec790087840ea66ee8ead6009ab2066bec9cbf5bcc394ccdf130e6139051d6bace334b3a66c4f05349585213172c + languageName: node + linkType: hard + +"@inquirer/figures@npm:^2.0.5": + version: 2.0.5 + resolution: "@inquirer/figures@npm:2.0.5" + checksum: 10c0/139671b88f33f059aec85ed3fdf464999115573350c6dea61141adc1cfd43d14742b6cb68150c2ca9baf5a1bae618f990ed89b4430ae768d415bbd19944c56df + languageName: node + linkType: hard + +"@inquirer/type@npm:^4.0.5": + version: 4.0.5 + resolution: "@inquirer/type@npm:4.0.5" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/390edb0fd1f027f9c8dc26bac28486d38bbde6c19974ef1588ea187f54a2cb58db639ebca31fa81a8fe4a4e84c2f0953ab3f5a6768ba86649368c5e806148a6f + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -2978,6 +3039,20 @@ __metadata: languageName: node linkType: hard +"@mswjs/interceptors@npm:^0.41.3": + version: 0.41.8 + resolution: "@mswjs/interceptors@npm:0.41.8" + dependencies: + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/logger": "npm:^0.3.0" + "@open-draft/until": "npm:^2.0.0" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + strict-event-emitter: "npm:^0.5.1" + checksum: 10c0/6ffe97fc7a84acc3d25464542b061e2525b6964c694c4fb325b91d01b61229124c457d5af75cf5eeb96969741769cc104d5d2d11e26d6dbae995aa9398fd9a7e + languageName: node + linkType: hard + "@ndelangen/get-tarball@npm:^3.0.7": version: 3.0.9 resolution: "@ndelangen/get-tarball@npm:3.0.9" @@ -3038,6 +3113,37 @@ __metadata: languageName: node linkType: hard +"@open-draft/deferred-promise@npm:^2.2.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 10c0/eafc1b1d0fc8edb5e1c753c5e0f3293410b40dde2f92688211a54806d4136887051f39b98c1950370be258483deac9dfd17cf8b96557553765198ef2547e4549 + languageName: node + linkType: hard + +"@open-draft/deferred-promise@npm:^3.0.0": + version: 3.0.0 + resolution: "@open-draft/deferred-promise@npm:3.0.0" + checksum: 10c0/4dd697e55495e436be9536413cc9975e792e9ca7472e81e3d3d69e9b65cb678465aac90b463ac02f2b490c0581c4e9aa8a33d2a5857decbe2c6d9ffb310f8e1f + languageName: node + linkType: hard + +"@open-draft/logger@npm:^0.3.0": + version: 0.3.0 + resolution: "@open-draft/logger@npm:0.3.0" + dependencies: + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.0" + checksum: 10c0/90010647b22e9693c16258f4f9adb034824d1771d3baa313057b9a37797f571181005bc50415a934eaf7c891d90ff71dcd7a9d5048b0b6bb438f31bef2c7c5c1 + languageName: node + linkType: hard + +"@open-draft/until@npm:^2.0.0": + version: 2.1.0 + resolution: "@open-draft/until@npm:2.1.0" + checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -5744,6 +5850,22 @@ __metadata: languageName: node linkType: hard +"@types/set-cookie-parser@npm:^2.4.10": + version: 2.4.10 + resolution: "@types/set-cookie-parser@npm:2.4.10" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/010b0c582ea70a2088618b4725808e80c30cce296c19ec58e51d94e0fd1038201b7b99238bf3ea74e1894163c8037d10a4f1729de62b2801ce240ff070f43e76 + languageName: node + linkType: hard + +"@types/statuses@npm:^2.0.6": + version: 2.0.6 + resolution: "@types/statuses@npm:2.0.6" + checksum: 10c0/dd88c220b0e2c6315686289525fd61472d2204d2e4bef4941acfb76bda01d3066f749ac74782aab5b537a45314fcd7d6261eefa40b6ec872691f5803adaa608d + languageName: node + linkType: hard + "@types/unist@npm:*, @types/unist@npm:^3.0.0": version: 3.0.3 resolution: "@types/unist@npm:3.0.3" @@ -6224,6 +6346,7 @@ __metadata: jsdom: "npm:^26.1.0" jwt-decode: "npm:^4.0.0" lodash.debounce: "npm:^4.0.8" + msw: "npm:^2.14.2" qrcode.react: "npm:^3.1.0" quill: "npm:2.0.3" react: "npm:^18.2.0" @@ -7248,6 +7371,24 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + "clone-deep@npm:^4.0.1": version: 4.0.1 resolution: "clone-deep@npm:4.0.1" @@ -7469,6 +7610,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.1.1": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c + languageName: node + linkType: hard + "copy-text-to-clipboard@npm:^3.0.1": version: 3.2.0 resolution: "copy-text-to-clipboard@npm:3.2.0" @@ -9010,6 +9158,31 @@ __metadata: languageName: node linkType: hard +"fast-string-truncated-width@npm:^3.0.2": + version: 3.0.3 + resolution: "fast-string-truncated-width@npm:3.0.3" + checksum: 10c0/043b8663397d14a3880ce4f3407bcda60b40db9bbeafe62863a35d1f9c69ea17c8da3fcd72de235553e6c9cd053128cde9e24ca0d4a7463208f48db3cd23d981 + languageName: node + linkType: hard + +"fast-string-width@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-string-width@npm:3.0.2" + dependencies: + fast-string-truncated-width: "npm:^3.0.2" + checksum: 10c0/c8822d175315bb353ebe782b65214ac53b13e3bf704e03b132ea7bdfa8de6a636375b3ab7a4097545393d109381c37c4f387c72a462c90b61412dbc4632f39a7 + languageName: node + linkType: hard + +"fast-wrap-ansi@npm:^0.2.0": + version: 0.2.0 + resolution: "fast-wrap-ansi@npm:0.2.0" + dependencies: + fast-string-width: "npm:^3.0.2" + checksum: 10c0/c0eb6debee565c5dbb9132dddff5c4d4aba5eb02185ae4dab285acd6186018cffca04264e92f373cbf592a9bcd1c33d65dba036030a8f3baeff1169969a1b59b + languageName: node + linkType: hard + "fastest-stable-stringify@npm:^2.0.2": version: 2.0.2 resolution: "fastest-stable-stringify@npm:2.0.2" @@ -9418,6 +9591,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + "get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": version: 2.0.2 resolution: "get-func-name@npm:2.0.2" @@ -9650,6 +9830,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:^16.13.2": + version: 16.13.2 + resolution: "graphql@npm:16.13.2" + checksum: 10c0/64e822a0a0e4398781e4bc9765b88d370c08261498b517add4b878038ef7be2005b6b394a79a5102b9379d57052f60bc7f23fec8f39808d101984a74772ebd9d + languageName: node + linkType: hard + "gunzip-maybe@npm:^1.4.2": version: 1.4.2 resolution: "gunzip-maybe@npm:1.4.2" @@ -9785,6 +9972,16 @@ __metadata: languageName: node linkType: hard +"headers-polyfill@npm:^5.0.1": + version: 5.0.1 + resolution: "headers-polyfill@npm:5.0.1" + dependencies: + "@types/set-cookie-parser": "npm:^2.4.10" + set-cookie-parser: "npm:^3.0.1" + checksum: 10c0/c269730a88a12c88718037aa71f178601f2b193ba8a37e276b6ced6b8f7e06fc1ac051f2a7acb0a8b4cc878407066555fdcdbb270e90374baaa472cb26af0c30 + languageName: node + linkType: hard + "hoist-non-react-statics@npm:^3.3.1": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" @@ -10320,6 +10517,13 @@ __metadata: languageName: node linkType: hard +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 10c0/5b24fda6776d00e42431d7bcd86bce81cb0b6cabeb944142fe7b077a54ada2e155066ad06dbe790abdb397884bdc3151e04a9707b8cd185099efbc79780573ed + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -11948,6 +12152,39 @@ __metadata: languageName: node linkType: hard +"msw@npm:^2.14.2": + version: 2.14.2 + resolution: "msw@npm:2.14.2" + dependencies: + "@inquirer/confirm": "npm:^6.0.11" + "@mswjs/interceptors": "npm:^0.41.3" + "@open-draft/deferred-promise": "npm:^3.0.0" + "@types/statuses": "npm:^2.0.6" + cookie: "npm:^1.1.1" + graphql: "npm:^16.13.2" + headers-polyfill: "npm:^5.0.1" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + path-to-regexp: "npm:^6.3.0" + picocolors: "npm:^1.1.1" + rettime: "npm:^0.11.7" + statuses: "npm:^2.0.2" + strict-event-emitter: "npm:^0.5.1" + tough-cookie: "npm:^6.0.1" + type-fest: "npm:^5.5.0" + until-async: "npm:^3.0.2" + yargs: "npm:^17.7.2" + peerDependencies: + typescript: ">= 4.8.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 10c0/85dcf819047167b925fcb55b19a38533697e2ea27f341f637a21d311482478ee568d87ccf4a65c388fb7e90c238976b6ae06db3271f53dbc99add2cbc6719b44 + languageName: node + linkType: hard + "mutation-observer@npm:^1.0.3": version: 1.0.3 resolution: "mutation-observer@npm:1.0.3" @@ -11955,6 +12192,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "mute-stream@npm:3.0.0" + checksum: 10c0/12cdb36a101694c7a6b296632e6d93a30b74401873cf7507c88861441a090c71c77a58f213acadad03bc0c8fa186639dec99d68a14497773a8744320c136e701 + languageName: node + linkType: hard + "nan@npm:^2.17.0": version: 2.20.0 resolution: "nan@npm:2.20.0" @@ -12365,6 +12609,13 @@ __metadata: languageName: node linkType: hard +"outvariant@npm:^1.4.0, outvariant@npm:^1.4.3": + version: 1.4.3 + resolution: "outvariant@npm:1.4.3" + checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c + languageName: node + linkType: hard + "p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -12565,6 +12816,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^6.3.0": + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -14413,6 +14671,13 @@ __metadata: languageName: node linkType: hard +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -14491,6 +14756,13 @@ __metadata: languageName: node linkType: hard +"rettime@npm:^0.11.7": + version: 0.11.11 + resolution: "rettime@npm:0.11.11" + checksum: 10c0/021fc9d9870ce04f032952e63fc5576f3f8e7c9c15513b1a479a64646df90239802eef6d60a98cbfb6ac87bb623d4f120a8ee71193d02984e3d2915c28695f6e + languageName: node + linkType: hard + "reusify@npm:^1.0.4": version: 1.0.4 resolution: "reusify@npm:1.0.4" @@ -14871,6 +15143,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^3.0.1": + version: 3.1.0 + resolution: "set-cookie-parser@npm:3.1.0" + checksum: 10c0/7465e389ff9fb7ff243fd55f0f48b5648d53a560903db170a7f766c2b82f22f4242c4d366fcdf12afdd376496f84058025d889e718459256ecbfc9a5fe7755f5 + languageName: node + linkType: hard + "set-function-length@npm:^1.1.1": version: 1.2.0 resolution: "set-function-length@npm:1.2.0" @@ -15202,6 +15481,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:^2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + "std-env@npm:^3.8.0": version: 3.10.0 resolution: "std-env@npm:3.10.0" @@ -15268,6 +15554,13 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.5.1": + version: 0.5.1 + resolution: "strict-event-emitter@npm:0.5.1" + checksum: 10c0/f5228a6e6b6393c57f52f62e673cfe3be3294b35d6f7842fc24b172ae0a6e6c209fa83241d0e433fc267c503bc2f4ffdbe41a9990ff8ffd5ac425ec0489417f7 + languageName: node + linkType: hard + "string-convert@npm:^0.2.0": version: 0.2.1 resolution: "string-convert@npm:0.2.1" @@ -15625,6 +15918,13 @@ __metadata: languageName: node linkType: hard +"tagged-tag@npm:^1.0.0": + version: 1.0.0 + resolution: "tagged-tag@npm:1.0.0" + checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1 + languageName: node + linkType: hard + "tar-fs@npm:^2.1.1": version: 2.1.1 resolution: "tar-fs@npm:2.1.1" @@ -15821,6 +16121,13 @@ __metadata: languageName: node linkType: hard +"tldts-core@npm:^7.0.30": + version: 7.0.30 + resolution: "tldts-core@npm:7.0.30" + checksum: 10c0/e3cd730e96b0e9c0332fcaab44d0257b668f9089644508e4f6f870d37bbf5c218243b7e83aa39690c87b386d1b0ad577772a5994969c4c81cc25a476f783ccd7 + languageName: node + linkType: hard + "tldts@npm:^6.1.32": version: 6.1.86 resolution: "tldts@npm:6.1.86" @@ -15832,6 +16139,17 @@ __metadata: languageName: node linkType: hard +"tldts@npm:^7.0.5": + version: 7.0.30 + resolution: "tldts@npm:7.0.30" + dependencies: + tldts-core: "npm:^7.0.30" + bin: + tldts: bin/cli.js + checksum: 10c0/c36f7b480f09128303158e4738a82426c33e8da9f77d4fb57a2d5ef5896c803d7a3c1d53ade965712f9cb4946935139b6d192a18698665556ca504493c7c265e + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -15885,6 +16203,15 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^6.0.1": + version: 6.0.1 + resolution: "tough-cookie@npm:6.0.1" + dependencies: + tldts: "npm:^7.0.5" + checksum: 10c0/ec70bd6b1215efe4ed31a158f0be3e4c9088fcbd8620edc23a5860d4f3d85c757b77e274baaa700f7b25e409f4181552ed189603c2b2e1a9f88104da3a61a37d + languageName: node + linkType: hard + "tr46@npm:^5.1.0": version: 5.1.1 resolution: "tr46@npm:5.1.1" @@ -16093,6 +16420,15 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^5.5.0": + version: 5.6.0 + resolution: "type-fest@npm:5.6.0" + dependencies: + tagged-tag: "npm:^1.0.0" + checksum: 10c0/5468a8ffda7f3904e6f7bbd8069eb8b6dd4bd9156e206df7a01d09a73e28cd1afedf74ead9d0fc12841c8c90074194859feca240511c50800962fde1bd9ddcbc + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -16407,6 +16743,13 @@ __metadata: languageName: node linkType: hard +"until-async@npm:^3.0.2": + version: 3.0.2 + resolution: "until-async@npm:3.0.2" + checksum: 10c0/61c8b03895dbe18fe3d90316d0a1894e0c131ea4b1673f6ce78eed993d0bb81bbf4b7adf8477e9ff7725782a76767eed9d077561cfc9f89b4a1ebe61f7c9828e + languageName: node + linkType: hard + "untildify@npm:^4.0.0": version: 4.0.0 resolution: "untildify@npm:4.0.0" @@ -16966,7 +17309,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -17076,6 +17419,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -17097,6 +17447,28 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + "yauzl@npm:^2.10.0": version: 2.10.0 resolution: "yauzl@npm:2.10.0" From ee0d084286e7f63c53387aa16d5d1a02b64d5b4a Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:29:20 +0900 Subject: [PATCH 03/12] =?UTF-8?q?chore(admin):=20msw=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/.gitignore | 1 + apps/admin/e2e/helpers/msw.ts | 12 + apps/admin/public/mockServiceWorker.js | 349 +++++++++++++++++++++++++ apps/admin/src/main.tsx | 15 +- apps/admin/src/mocks/browser.ts | 5 + apps/admin/src/mocks/handlers.ts | 199 ++++++++++++++ apps/admin/src/vite-env.d.ts | 4 + 7 files changed, 584 insertions(+), 1 deletion(-) create mode 100644 apps/admin/e2e/helpers/msw.ts create mode 100644 apps/admin/public/mockServiceWorker.js create mode 100644 apps/admin/src/mocks/browser.ts create mode 100644 apps/admin/src/mocks/handlers.ts diff --git a/apps/admin/.gitignore b/apps/admin/.gitignore index a547bf36..2df5e8ed 100644 --- a/apps/admin/.gitignore +++ b/apps/admin/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +.last-run.json \ No newline at end of file diff --git a/apps/admin/e2e/helpers/msw.ts b/apps/admin/e2e/helpers/msw.ts new file mode 100644 index 00000000..5d0ec129 --- /dev/null +++ b/apps/admin/e2e/helpers/msw.ts @@ -0,0 +1,12 @@ +import type { Page } from '@playwright/test'; + +export const prepareE2EContext = async (page: Page, scenario: string = 'default') => { + await page.addInitScript((currentScenario) => { + (window as Window & { __ENABLE_E2E_MSW__?: boolean }).__ENABLE_E2E_MSW__ = true; + localStorage.setItem('__E2E_SCENARIO__', currentScenario); + }, scenario); + + await page.goto('/'); + await page.waitForTimeout(300); + await page.reload(); +}; diff --git a/apps/admin/public/mockServiceWorker.js b/apps/admin/public/mockServiceWorker.js new file mode 100644 index 00000000..a1e52b47 --- /dev/null +++ b/apps/admin/public/mockServiceWorker.js @@ -0,0 +1,349 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.14.2' +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() + + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been terminated (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + interceptedAt: requestInterceptedAt, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/apps/admin/src/main.tsx b/apps/admin/src/main.tsx index 0d3cae1d..d7510f81 100644 --- a/apps/admin/src/main.tsx +++ b/apps/admin/src/main.tsx @@ -2,4 +2,17 @@ import ReactDOM from 'react-dom/client'; import App from './App'; -ReactDOM.createRoot(document.getElementById('root')!).render(); +const bootstrap = async () => { + const enableE2EMSW = (window as Window & { __ENABLE_E2E_MSW__?: boolean }).__ENABLE_E2E_MSW__ === true; + if (enableE2EMSW) { + const { worker } = await import('./mocks/browser'); + await worker.start({ + onUnhandledRequest: 'bypass', + serviceWorker: { url: '/mockServiceWorker.js' }, + }); + } + + ReactDOM.createRoot(document.getElementById('root')!).render(); +}; + +void bootstrap(); diff --git a/apps/admin/src/mocks/browser.ts b/apps/admin/src/mocks/browser.ts new file mode 100644 index 00000000..c95883bc --- /dev/null +++ b/apps/admin/src/mocks/browser.ts @@ -0,0 +1,5 @@ +import { setupWorker } from 'msw/browser'; + +import { handlers } from './handlers'; + +export const worker = setupWorker(...handlers); diff --git a/apps/admin/src/mocks/handlers.ts b/apps/admin/src/mocks/handlers.ts new file mode 100644 index 00000000..3fc8d257 --- /dev/null +++ b/apps/admin/src/mocks/handlers.ts @@ -0,0 +1,199 @@ +import { HttpResponse, http, type JsonBodyType } from 'msw'; + +const emptyPage = { + content: [], + empty: true, + first: true, + last: true, + number: 0, + numberOfElements: 0, + size: 100, + totalElements: 0, + totalPages: 0, + pageable: { + offset: 0, + pageNumber: 0, + pageSize: 100, + paged: true, + unpaged: false, + sort: { sorted: false, unsorted: true, empty: true }, + }, + sort: { sorted: false, unsorted: true, empty: true }, +}; + +const json = (body: JsonBodyType, status = 200) => HttpResponse.json(body, { status }); + +const getScenario = () => window.localStorage.getItem('__E2E_SCENARIO__') ?? 'default'; + +const SHOW_NAME = '기능 테스트 공연'; +const PARTICIPANT_NAME = '홍길동'; +const TICKET_NAME = '일반 티켓'; +const COMMON_TIME = '2026-05-01T10:00:00.000Z'; + +const popupHome = { + id: 1, + type: 'NOTICE', + description: '테스트 공지', + noticeTitle: '공지', + eventUrl: 'https://example.com', + startDate: '2099-01-01T00:00:00.000Z', + endDate: '2099-12-31T23:59:59.999Z', +}; + +const showDetail = { + id: 1, + name: SHOW_NAME, + date: '2099-12-31T12:00:00.000Z', +}; + +const participantSummary = { + reservationId: 501, + reservationName: PARTICIPANT_NAME, + userId: 100, + salesTicketTypeId: 10, + salesTicketTypeName: TICKET_NAME, + ticketCount: 1, + answeredAt: COMMON_TIME, +}; + +export const handlers = [ + http.all('*/web/papi/v1/popup/HOME', () => json(popupHome)), + http.get('*/web/v1/host/shows/1', () => json(showDetail)), + http.get('*/web/v1/users/me', () => + json({ + id: 10, + imgPath: null, + nickname: '테스터', + userCode: 'TESTER001', + }), + ), + http.get('*/web/v1/host/shows', () => json([])), + http.get('*/web/v1/host/users/me/summaries', () => + json({ + nickname: '테스터', + profileImagePath: null, + userCode: 'TESTER001', + }), + ), + http.get('*/web/v1/shows/1/hosts/me', () => + json({ + id: 1, + hostName: '테스터', + imagePath: null, + type: 'MAIN', + }), + ), + http.get('*/web/v1/host/shows/1/sales-infos', () => json({ salesStartDateTime: null, salesEndDateTime: null })), + http.get('*/web/v1/host/shows/1/settlement-infos', () => + json({ + bankAccount: '카카오뱅크 123-456', + idCardPhotoFile: { url: 'https://example.com/id.pdf', fileName: 'id.pdf' }, + settlementBankAccountPhotoFile: { url: 'https://example.com/bank.pdf', fileName: 'bank.pdf' }, + }), + ), + http.get('*/web/v1/host/shows/1/settlement-events/last', () => + json({ settlementEventType: 'SEND', triggeredAt: '2026-05-01T00:00:00.000Z' }), + ), + http.get('*/web/v1/shows/1/settlement-summaries', () => + json({ + salesAmount: 1200000, + actual: null, + expected: { fee: 120000, settlementAmount: 1080000 }, + }), + ), + http.all('*/web/v1/host/settlement-banners', ({ request }) => { + if (request.method === 'POST') return json({}); + return json([{ showId: 1, bannerType: 'REQUIRED', showName: SHOW_NAME }]); + }), + http.get('*/web/v1/host/shows/1/settlement-statements/last/file', () => + new HttpResponse('%PDF-1.4\n1 0 obj\n<<>>\nendobj\ntrailer\n<<>>\n%%EOF', { + status: 200, + headers: { 'content-type': 'application/pdf' }, + }), + ), + http.post('*/web/v1/host/shows/1/settlement-request', () => { + if (getScenario() === 'settlement-request-fail') { + return json({ message: 'fail' }, 500); + } + return json({}); + }), + http.get('*/web/v1/host/shows/1/pre-questions', () => + json({ + preQuestions: [ + { + id: 101, + question: '관람 전 확인하고 싶은 내용이 있나요?', + description: '자유 입력', + isRequired: true, + sequence: 1, + }, + ], + totalRespondentCount: 2, + }), + ), + http.get('*/web/v1/host/shows/1/sales-ticket-types/summary', () => json([{ id: 10, ticketType: 'SALE', ticketName: '일반 티켓' }])), + http.get('*/web/v1/host/shows/1/pre-question-answers/questions/101', () => + json({ + ...emptyPage, + content: [ + { + id: 1, + preQuestionId: 101, + userId: 100, + reservationName: PARTICIPANT_NAME, + salesTicketTypeId: 10, + salesTicketTypeName: TICKET_NAME, + reservationId: 501, + ticketCount: 1, + answer: '현장 주차 가능 여부', + createdAt: COMMON_TIME, + modifiedAt: COMMON_TIME, + }, + ], + empty: false, + numberOfElements: 1, + totalElements: 1, + totalPages: 1, + }), + ), + http.get('*/web/v1/host/shows/1/pre-question-answers/participants/501', () => + json({ + reservationId: 501, + userId: 100, + reservationName: PARTICIPANT_NAME, + salesTicketTypeId: 10, + salesTicketTypeName: TICKET_NAME, + ticketCount: 1, + createdAt: COMMON_TIME, + modifiedAt: COMMON_TIME, + answers: [ + { + preQuestionId: 101, + question: '관람 전 확인하고 싶은 내용이 있나요?', + description: '자유 입력', + isRequired: true, + answer: '현장 주차 가능 여부', + sequence: 1, + createdAt: COMMON_TIME, + modifiedAt: COMMON_TIME, + }, + ], + }), + ), + http.get('*/web/v1/host/shows/1/pre-question-answers/participants', ({ request }) => { + const url = new URL(request.url); + const reservationName = url.searchParams.get('reservationName'); + const noMatchedReservation = !!reservationName && reservationName !== PARTICIPANT_NAME; + + return json({ + ...emptyPage, + content: noMatchedReservation ? [] : [participantSummary], + empty: noMatchedReservation, + numberOfElements: noMatchedReservation ? 0 : 1, + totalElements: noMatchedReservation ? 0 : 1, + totalPages: noMatchedReservation ? 0 : 1, + }); + }), + http.all('*/web/*', () => json({})), + http.all('*/sa-api/*', () => json({})), +]; diff --git a/apps/admin/src/vite-env.d.ts b/apps/admin/src/vite-env.d.ts index f255ee58..5b4bbe4e 100644 --- a/apps/admin/src/vite-env.d.ts +++ b/apps/admin/src/vite-env.d.ts @@ -8,3 +8,7 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +interface Window { + __ENABLE_E2E_MSW__?: boolean; +} From 05c26c8bf9538408f26f1ee418f0e4a4db52e46a Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:32:36 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat(admin):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20usePopupDialog=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HomePage/HomePage.integration.test.tsx | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 apps/admin/src/pages/HomePage/HomePage.integration.test.tsx diff --git a/apps/admin/src/pages/HomePage/HomePage.integration.test.tsx b/apps/admin/src/pages/HomePage/HomePage.integration.test.tsx new file mode 100644 index 00000000..3da09bfb --- /dev/null +++ b/apps/admin/src/pages/HomePage/HomePage.integration.test.tsx @@ -0,0 +1,146 @@ +// @vitest-environment jsdom +// 통합 테스트 목적: +// 1) HomePage가 HOME 팝업 데이터를 조회하는지 검증 +// 2) 조회된 popupData를 usePopupDialog에 전달하는지 검증 +import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Popup } from '@boolti/api'; +import type { ReactNode } from 'react'; + +const mockUsePopup = vi.fn(); +const mockUsePopupDialog = vi.fn(); + +vi.mock('@boolti/api', () => ({ + queryKeys: { + user: { + summary: { queryKey: ['user', 'summary'] }, + }, + }, + useLogout: () => ({ mutateAsync: vi.fn() }), + usePopup: (...args: unknown[]) => mockUsePopup(...args), + useQueryClient: () => ({ removeQueries: vi.fn() }), + useSettlementBanners: () => ({ data: [] }), + useShowList: () => ({ data: [], isLoading: false }), + useUserProfile: () => ({ data: null, isLoading: false }), +})); + +vi.mock('@boolti/ui', () => ({ + Footer: () =>
Footer
, + useConfirm: () => vi.fn().mockResolvedValue(false), + useDialog: () => ({ id: 'dialog', isOpen: false, open: vi.fn(), close: vi.fn() }), +})); + +vi.mock('@boolti/bridge', () => ({ + checkIsWebView: () => false, +})); + +vi.mock('~/components/Header', () => ({ + default: () =>
Header
, +})); + +vi.mock('~/components/Layout', () => ({ + default: ({ + children, + header, + headerMenu, + banner, + }: { + children: ReactNode; + header?: ReactNode; + headerMenu?: ReactNode; + banner?: ReactNode; + }) => ( +
+ {header} + {headerMenu} + {banner} + {children} +
+ ), +})); + +vi.mock('~/components/ShowList', () => ({ + default: () =>
ShowList
, +})); + +vi.mock('~/components/UserProfile', () => ({ + default: () =>
UserProfile
, +})); + +vi.mock('~/components/ProfileDropdown', () => ({ + default: () => , +})); + +vi.mock('~/components/SettingDialogContent', () => ({ + default: () =>
SettingDialogContent
, +})); + +vi.mock('~/components/ShowTypeSelectDialogContent', () => ({ + default: () =>
ShowTypeSelectDialogContent
, +})); + +vi.mock('~/atoms/useAuthAtom', () => ({ + useAuthAtom: () => ({ removeToken: vi.fn() }), +})); + +vi.mock('~/hooks/usePopupDialog', () => ({ + default: (...args: unknown[]) => mockUsePopupDialog(...args), +})); + +vi.mock('./HomePage.styles', () => ({ + default: { + Logo: ({ children }: { children: ReactNode }) =>
{children}
, + ProfileDropdown: ({ children }: { children: ReactNode }) =>
{children}
, + ProfileDropdownMobile: ({ children }: { children: ReactNode }) =>
{children}
, + HeaderMenu: ({ children }: { children: ReactNode }) =>
{children}
, + HeaderMenuItemButton: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void | Promise; + }) => ( + + ), + BannerContainer: ({ children }: { children: ReactNode }) =>
{children}
, + Banner: ({ children }: { children: ReactNode }) =>
{children}
, + BannerDescription: ({ children }: { children: ReactNode }) =>
{children}
, + BannerShowTitle: ({ children }: { children: ReactNode }) => {children}, + BannerLink: ({ children }: { children: ReactNode }) => {children}, + Container: ({ children }: { children: ReactNode }) =>
{children}
, + }, +})); + +import HomePage from './index'; + +describe('HomePage integration', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('HOME 팝업 데이터를 조회하고 usePopupDialog에 전달한다', () => { + const popupData = { + id: 11, + type: 'EVENT', + description: 'banner', + noticeTitle: 'title', + eventUrl: 'https://example.com', + startDate: '2026-01-01T00:00:00.000Z', + endDate: '2026-12-31T23:59:59.999Z', + } satisfies Popup; + + mockUsePopup.mockReturnValue({ data: popupData }); + + render( + + + , + ); + + expect(mockUsePopup).toHaveBeenCalledWith('HOME'); + expect(mockUsePopupDialog).toHaveBeenCalledWith(popupData); + }); +}); From 1fbd803064dbf42964f4d5ccc5ea7439d247f393 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:33:16 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat(admin):=20=EC=82=AC=EC=A0=84=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=ED=83=AD=20=EC=A0=84=ED=99=98=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResponseTab.integration.test.tsx | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 apps/admin/src/components/ShowPreQuestion/ResponseTab/ResponseTab.integration.test.tsx diff --git a/apps/admin/src/components/ShowPreQuestion/ResponseTab/ResponseTab.integration.test.tsx b/apps/admin/src/components/ShowPreQuestion/ResponseTab/ResponseTab.integration.test.tsx new file mode 100644 index 00000000..b62d0a3a --- /dev/null +++ b/apps/admin/src/components/ShowPreQuestion/ResponseTab/ResponseTab.integration.test.tsx @@ -0,0 +1,140 @@ +// @vitest-environment jsdom +// 통합 테스트 목적: +// 1) 응답자가 0명일 때 EmptyView가 노출되는지 검증 +// 2) 질문별/참여자별 뷰 전환이 동작하는지 검증 +// 3) 모바일 질문별 뷰에서 정렬 토글이 최신/오래된 순으로 전환되는지 검증 +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { forwardRef, type ReactNode } from 'react'; + +import type { PreQuestionItem } from '@boolti/api/src/types'; + +const mockIsMobile = vi.fn(); + +vi.mock('@boolti/api', () => ({ + usePreQuestionAnswersList: () => [{ data: { content: [], totalElements: 0 }, isLoading: false, isFetching: false }], + usePreQuestionParticipants: () => ({ data: { content: [] } }), + usePreQuestionParticipantDetail: () => ({ data: null }), + useSalesTicketTypesSummary: () => ({ data: [] }), +})); + +vi.mock('~/hooks/useIsMobile', () => ({ + useIsMobile: () => mockIsMobile(), +})); + +vi.mock('./EmptyView', () => ({ + default: () =>
EmptyView
, +})); + +vi.mock('./QuestionResponseView', () => ({ + default: () =>
QuestionResponseView
, +})); + +vi.mock('./ParticipantResponseView', () => ({ + default: () =>
ParticipantResponseView
, +})); + +vi.mock('~/components/TicketNameFilter', () => ({ + default: () =>
TicketNameFilter
, +})); + +vi.mock('./ResponseTab.styles', () => ({ + default: { + Container: ({ children }: { children: ReactNode }) =>
{children}
, + HeaderContainer: ({ children }: { children: ReactNode }) =>
{children}
, + SegmentButtonContainer: ({ children }: { children: ReactNode }) =>
{children}
, + SegmentButton: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void; + }) => ( + + ), + SortContainer: ({ children }: { children: ReactNode }) =>
{children}
, + MobileQuestionSortToggle: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void; + }) => ( + + ), + SortDropdown: forwardRef(({ children }, ref) => ( +
{children}
+ )), + SortButton: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void; + }) => ( + + ), + SortMenu: ({ children }: { children: ReactNode }) =>
{children}
, + SortMenuItem: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void; + }) => ( + + ), + }, +})); + +import ResponseTab from './index'; + +const questions: PreQuestionItem[] = [ + { id: 1, question: '질문 1', isRequired: true, sequence: 1 }, +]; + +describe('ResponseTab integration', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockIsMobile.mockReturnValue(false); + }); + + afterEach(() => { + cleanup(); + }); + + it('응답자가 0명이면 EmptyView를 렌더링한다', () => { + render(); + expect(screen.getByText('EmptyView')).toBeTruthy(); + }); + + it('질문별/참여자별 탭 전환이 동작한다', () => { + render(); + + expect(screen.getByText('QuestionResponseView')).toBeTruthy(); + fireEvent.click(screen.getByRole('button', { name: '참여자별 응답' })); + expect(screen.getByText('ParticipantResponseView')).toBeTruthy(); + }); + + it('모바일 질문별 뷰에서 정렬 토글 시 레이블이 전환된다', () => { + mockIsMobile.mockReturnValue(true); + render(); + + const latestButtons = screen.getAllByRole('button', { name: /최신 순/ }); + const sortToggle = latestButtons[latestButtons.length - 1]; + expect(sortToggle).toBeTruthy(); + if (!sortToggle) { + return; + } + fireEvent.click(sortToggle); + expect(screen.getByRole('button', { name: /오래된 순/ })).toBeTruthy(); + }); +}); From ab3fdd6a33b95a87239fc57f224503e6e7e7d8b4 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:33:42 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat(admin):=20=EB=9E=9C=EB=94=A9=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20usePopupDialog?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Landing/Landing.integration.test.tsx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 apps/admin/src/pages/Landing/Landing.integration.test.tsx diff --git a/apps/admin/src/pages/Landing/Landing.integration.test.tsx b/apps/admin/src/pages/Landing/Landing.integration.test.tsx new file mode 100644 index 00000000..29b65050 --- /dev/null +++ b/apps/admin/src/pages/Landing/Landing.integration.test.tsx @@ -0,0 +1,66 @@ +// @vitest-environment jsdom +// 통합 테스트 목적: +// 1) LandingPage가 HOME 팝업 데이터를 조회하는지 검증 +// 2) 조회된 popupData를 usePopupDialog에 전달하는지 검증 +import { render } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Popup } from '@boolti/api'; +import type { ReactNode } from 'react'; + +const mockUsePopup = vi.fn(); +const mockUsePopupDialog = vi.fn(); + +vi.mock('@boolti/api', () => ({ + usePopup: (...args: unknown[]) => mockUsePopup(...args), +})); + +vi.mock('@boolti/ui', () => ({ + Footer: () =>
Footer
, +})); + +vi.mock('~/hooks/usePopupDialog', () => ({ + default: (...args: unknown[]) => mockUsePopupDialog(...args), +})); + +vi.mock('./components', () => ({ + Header: () =>
Header
, + Hero: () =>
Hero
, + HowToUse: () =>
HowToUse
, + Problem: () =>
Problem
, + SolutionFeatures: () =>
SolutionFeatures
, + SolutionHighlight: () =>
SolutionHighlight
, +})); + +vi.mock('./LandingPage.styles', () => ({ + default: { + Container: ({ children }: { children: ReactNode }) =>
{children}
, + FooterContainer: ({ children }: { children: ReactNode }) =>
{children}
, + }, +})); + +import LandingPage from './index'; + +describe('LandingPage integration', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('HOME 팝업 데이터를 조회하고 usePopupDialog에 전달한다', () => { + const popupData = { + id: 22, + type: 'NOTICE', + description: 'notice', + noticeTitle: '공지', + eventUrl: 'https://example.com', + startDate: '2026-01-01T00:00:00.000Z', + endDate: '2026-12-31T23:59:59.999Z', + } satisfies Popup; + + mockUsePopup.mockReturnValue({ data: popupData }); + + render(); + + expect(mockUsePopup).toHaveBeenCalledWith('HOME'); + expect(mockUsePopupDialog).toHaveBeenCalledWith(popupData); + }); +}); From e8ec6089526efd7b8e151a02314a0f863479fc51 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:34:20 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat(admin):=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=84=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20?= =?UTF-8?q?e2e=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/functional/auth-routing.e2e.spec.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 apps/admin/e2e/functional/auth-routing.e2e.spec.ts diff --git a/apps/admin/e2e/functional/auth-routing.e2e.spec.ts b/apps/admin/e2e/functional/auth-routing.e2e.spec.ts new file mode 100644 index 00000000..f4a0640e --- /dev/null +++ b/apps/admin/e2e/functional/auth-routing.e2e.spec.ts @@ -0,0 +1,45 @@ +import { expect, test } from '@playwright/test'; + +import { prepareE2EContext } from '../helpers/msw'; + +test.describe('Auth routing guard', () => { + test.beforeEach(async ({ page }) => { + await prepareE2EContext(page); + }); + + test('비로그인 + 일반 브라우저는 /home 접근 시 /login으로 이동한다', async ({ page }) => { + await page.addInitScript(() => { + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + }); + + await page.goto('/home'); + await expect(page).toHaveURL(/\/login$/); + await expect(page.getByText('로그인')).toBeVisible(); + }); + + test('로그인 상태는 /home 접근이 허용된다', async ({ page }) => { + await page.addInitScript(() => { + localStorage.setItem('accessToken', 'access-token'); + localStorage.setItem('refreshToken', 'refresh-token'); + }); + + await page.goto('/home'); + await expect(page).toHaveURL(/\/home$/); + await expect(page.getByText('로그인')).toHaveCount(0); + }); + + test('비로그인이라도 webview userAgent면 /home 접근이 허용된다', async ({ page }) => { + await page.addInitScript(() => { + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + Object.defineProperty(window.navigator, 'userAgent', { + configurable: true, + value: 'Mozilla/5.0 BOOLTI/ANDROID', + }); + }); + + await page.goto('/home'); + await expect(page).toHaveURL(/\/home$/); + }); +}); From 063ab5f0d0063908a98aa4f54bb67368af7b4a5d Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:34:38 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat(admin):=20=EC=82=AC=EC=A0=84=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=EC=9D=91=EB=8B=B5=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=ED=83=AD=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20e2e=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prequestion-response-flow.e2e.spec.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/admin/e2e/functional/prequestion-response-flow.e2e.spec.ts diff --git a/apps/admin/e2e/functional/prequestion-response-flow.e2e.spec.ts b/apps/admin/e2e/functional/prequestion-response-flow.e2e.spec.ts new file mode 100644 index 00000000..e30dcba5 --- /dev/null +++ b/apps/admin/e2e/functional/prequestion-response-flow.e2e.spec.ts @@ -0,0 +1,37 @@ +import { expect, test } from '@playwright/test'; + +import { prepareE2EContext } from '../helpers/msw'; + +test.describe('Pre-question response flow', () => { + test.beforeEach(async ({ page }) => { + await prepareE2EContext(page); + await page.addInitScript(() => { + localStorage.setItem('accessToken', 'access-token'); + localStorage.setItem('refreshToken', 'refresh-token'); + }); + }); + + test('응답 확인 탭에서 참여자별 응답/검색 플로우가 동작한다', async ({ page }) => { + await page.goto('/show/1/pre-question'); + await expect(page.getByText('응답 확인')).toBeVisible(); + + await page.getByText('응답 확인').click(); + await page.getByRole('button', { name: '참여자별 응답' }).click(); + + await expect(page.getByText('홍길동', { exact: true })).toBeVisible(); + await page.getByPlaceholder('참여자명 검색').fill('홍길동'); + await page.waitForTimeout(400); + await expect(page.getByText('홍길동', { exact: true })).toBeVisible(); + }); + + test('검색 결과가 없으면 빈 상태 메시지가 노출된다', async ({ page }) => { + await page.goto('/show/1/pre-question'); + await page.getByText('응답 확인').click(); + await page.getByRole('button', { name: '참여자별 응답' }).click(); + + await page.getByPlaceholder('참여자명 검색').fill('없는이름'); + await page.waitForTimeout(400); + + await expect(page.getByText('검색 결과가 없어요.')).toBeVisible(); + }); +}); From d6f351343b357dcc2fd1274c73ef8fe6049ffe1c Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 18:35:04 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat(admin):=20=EC=A0=95=EC=82=B0=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=B2=84=ED=8A=BC=20=ED=99=9C=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20e2e=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settlement-request-flow.e2e.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 apps/admin/e2e/functional/settlement-request-flow.e2e.spec.ts diff --git a/apps/admin/e2e/functional/settlement-request-flow.e2e.spec.ts b/apps/admin/e2e/functional/settlement-request-flow.e2e.spec.ts new file mode 100644 index 00000000..66d7cd08 --- /dev/null +++ b/apps/admin/e2e/functional/settlement-request-flow.e2e.spec.ts @@ -0,0 +1,36 @@ +import { expect, test } from '@playwright/test'; + +import { prepareE2EContext } from '../helpers/msw'; + +test.describe('Settlement request flow', () => { + test.beforeEach(async ({ page }) => { + await prepareE2EContext(page); + await page.addInitScript(() => { + localStorage.setItem('accessToken', 'access-token'); + localStorage.setItem('refreshToken', 'refresh-token'); + }); + }); + + test('동의 체크 전 비활성, 체크 후 활성화되고 요청이 전송된다', async ({ page }) => { + await page.goto('/show/1/settlement'); + const submitButton = page.getByRole('button', { name: '정산 요청하기' }); + await expect(submitButton).toBeDisabled(); + + await page.getByText('정산 내역 및 안내사항을 모두 확인하였으며 정산을 요청합니다.').click(); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + + await expect(page.getByText('정산을 요청했습니다')).toBeVisible(); + }); + + test('요청 실패 시 에러 토스트가 노출된다', async ({ page }) => { + await page.goto('/show/1/settlement'); + await page.evaluate(() => { + localStorage.setItem('__E2E_SCENARIO__', 'settlement-request-fail'); + }); + await page.getByText('정산 내역 및 안내사항을 모두 확인하였으며 정산을 요청합니다.').click(); + await page.getByRole('button', { name: '정산 요청하기' }).click(); + + await expect(page.getByText('정산 요청에 실패했습니다. 잠시 후에 다시 시도해주세요.')).toBeVisible(); + }); +}); From 661cb67b76c8ed00a893ffb808c872ccfce582b1 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 19:54:14 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat(admin):=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=20=EA=B4=80=EB=A0=A8=20e2e=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=95=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/functional/auth-routing.e2e.spec.ts | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/apps/admin/e2e/functional/auth-routing.e2e.spec.ts b/apps/admin/e2e/functional/auth-routing.e2e.spec.ts index f4a0640e..18e702a7 100644 --- a/apps/admin/e2e/functional/auth-routing.e2e.spec.ts +++ b/apps/admin/e2e/functional/auth-routing.e2e.spec.ts @@ -15,31 +15,7 @@ test.describe('Auth routing guard', () => { await page.goto('/home'); await expect(page).toHaveURL(/\/login$/); - await expect(page.getByText('로그인')).toBeVisible(); - }); - - test('로그인 상태는 /home 접근이 허용된다', async ({ page }) => { - await page.addInitScript(() => { - localStorage.setItem('accessToken', 'access-token'); - localStorage.setItem('refreshToken', 'refresh-token'); - }); - - await page.goto('/home'); - await expect(page).toHaveURL(/\/home$/); - await expect(page.getByText('로그인')).toHaveCount(0); - }); - - test('비로그인이라도 webview userAgent면 /home 접근이 허용된다', async ({ page }) => { - await page.addInitScript(() => { - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - Object.defineProperty(window.navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 BOOLTI/ANDROID', - }); - }); - - await page.goto('/home'); - await expect(page).toHaveURL(/\/home$/); + await expect(page.getByRole('heading', { name: '로그인' })).toBeVisible(); + await expect(page.getByRole('button', { name: '카카오톡으로 시작하기' })).toBeVisible(); }); }); From 73ddf19a5f64471d892fc7514e85c9c4394f84c9 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 19:55:21 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat(admin):=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=20=EB=B6=84=EA=B8=B0=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/App.auth-routing.integration.test.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 apps/admin/src/App.auth-routing.integration.test.tsx diff --git a/apps/admin/src/App.auth-routing.integration.test.tsx b/apps/admin/src/App.auth-routing.integration.test.tsx new file mode 100644 index 00000000..43449554 --- /dev/null +++ b/apps/admin/src/App.auth-routing.integration.test.tsx @@ -0,0 +1,79 @@ +// @vitest-environment jsdom +// 통합 테스트 목적: +// 1) 비로그인 + 일반 브라우저면 /home 접근 시 /login으로 리다이렉트되는지 검증 +// 2) 로그인 상태면 /home 접근이 허용되는지 검증 +// 3) 비로그인이라도 webview면 /home 접근이 허용되는지 검증 +import { cleanup, render, screen } from '@testing-library/react'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +const mockIsLogin = vi.fn<() => boolean>(); +const mockCheckIsWebView = vi.fn<() => boolean>(); + +vi.mock('~/atoms/useAuthAtom', () => ({ + useAuthAtom: () => ({ + isLogin: mockIsLogin, + }), +})); + +vi.mock('@boolti/bridge', () => ({ + checkIsWebView: () => mockCheckIsWebView(), +})); + +vi.mock('react-router-dom', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + ScrollRestoration: () => null, + }; +}); + +import PrivateRoute from './routes/PrivateRoute'; + +const renderWithRoute = (isLogin: boolean, isWebView: boolean) => { + mockIsLogin.mockReturnValue(isLogin); + mockCheckIsWebView.mockReturnValue(isWebView); + + render( + + + 로그인 페이지} /> + }> + 홈 페이지} /> + + + , + ); +}; + +describe('PrivateRoute integration', () => { + beforeEach(() => { + vi.clearAllMocks(); + window.scrollTo = vi.fn(); + }); + + afterEach(() => { + cleanup(); + }); + + it('비로그인 + 일반 브라우저면 /login으로 이동한다', async () => { + renderWithRoute(false, false); + + expect(await screen.findByText('로그인 페이지')).toBeTruthy(); + expect(screen.queryByText('홈 페이지')).toBeNull(); + }); + + it('로그인 상태면 /home 접근이 허용된다', async () => { + renderWithRoute(true, false); + + expect(await screen.findByText('홈 페이지')).toBeTruthy(); + expect(screen.queryByText('로그인 페이지')).toBeNull(); + }); + + it('비로그인이라도 webview면 /home 접근이 허용된다', async () => { + renderWithRoute(false, true); + + expect(await screen.findByText('홈 페이지')).toBeTruthy(); + expect(screen.queryByText('로그인 페이지')).toBeNull(); + }); +}); From b6d695f0c090190e58932194397c40de4b99abd7 Mon Sep 17 00:00:00 2001 From: hexdrinker Date: Mon, 4 May 2026 19:56:24 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix(admin):=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=9A=A9=EC=9D=B4=ED=95=98=EB=8F=84=EB=A1=9D=20PrivateRoute?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/e2e/helpers/msw.ts | 2 +- apps/admin/src/App.tsx | 20 +------------------- apps/admin/src/main.tsx | 15 +++++++++++---- apps/admin/src/routes/PrivateRoute.tsx | 25 +++++++++++++++++++++++++ apps/admin/src/vite-env.d.ts | 1 + 5 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 apps/admin/src/routes/PrivateRoute.tsx diff --git a/apps/admin/e2e/helpers/msw.ts b/apps/admin/e2e/helpers/msw.ts index 5d0ec129..c1443871 100644 --- a/apps/admin/e2e/helpers/msw.ts +++ b/apps/admin/e2e/helpers/msw.ts @@ -7,6 +7,6 @@ export const prepareE2EContext = async (page: Page, scenario: string = 'default' }, scenario); await page.goto('/'); - await page.waitForTimeout(300); + await page.waitForFunction(() => window.__E2E_MSW_READY__ === true, null, { timeout: 5000 }); await page.reload(); }; diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index cd334918..aecd0d62 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -19,7 +19,6 @@ import { import AuthErrorBoundary from './components/ErrorBoundary/AuthErrorBoundary'; import { PATH } from './constants/routes'; -import { useAuthAtom } from './atoms/useAuthAtom'; import GlobalErrorBoundary from './components/ErrorBoundary/GlobalErrorBoundary'; import { LandingPage, @@ -46,10 +45,10 @@ import ShowReservationPage from './pages/ShowReservationPage'; import ShowSettlementPage from './pages/ShowSettlementPage'; import ShowEnterancePage from './pages/ShowEnterancePage'; import { initVConsole } from './utils/vConsole'; -import { checkIsWebView } from '@boolti/bridge'; import { X_NCP_APIGW_API_KEY_ID } from './constants/ncp'; import { IS_PRODUCTION_PHASE } from './constants/phase'; import WebView from './pages/WebView'; +import PrivateRoute from './routes/PrivateRoute'; setDefaultOptions({ locale: ko }); @@ -110,23 +109,6 @@ const publicRoutes = [ }, ]; -const PrivateRoute = () => { - const { isLogin } = useAuthAtom(); - - if (!isLogin() && !checkIsWebView()) { - return ; - } - - return ( - <> - - - - - - ); -}; - const privateRoutes = [ { element: ( diff --git a/apps/admin/src/main.tsx b/apps/admin/src/main.tsx index d7510f81..84d22f5a 100644 --- a/apps/admin/src/main.tsx +++ b/apps/admin/src/main.tsx @@ -6,10 +6,17 @@ const bootstrap = async () => { const enableE2EMSW = (window as Window & { __ENABLE_E2E_MSW__?: boolean }).__ENABLE_E2E_MSW__ === true; if (enableE2EMSW) { const { worker } = await import('./mocks/browser'); - await worker.start({ - onUnhandledRequest: 'bypass', - serviceWorker: { url: '/mockServiceWorker.js' }, - }); + void worker + .start({ + onUnhandledRequest: 'bypass', + serviceWorker: { url: '/mockServiceWorker.js' }, + }) + .then(() => { + window.__E2E_MSW_READY__ = true; + }) + .catch(() => { + window.__E2E_MSW_READY__ = false; + }); } ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/apps/admin/src/routes/PrivateRoute.tsx b/apps/admin/src/routes/PrivateRoute.tsx new file mode 100644 index 00000000..7fd0acbe --- /dev/null +++ b/apps/admin/src/routes/PrivateRoute.tsx @@ -0,0 +1,25 @@ +import { checkIsWebView } from '@boolti/bridge'; +import { Navigate, Outlet, ScrollRestoration } from 'react-router-dom'; +import { Suspense } from 'react'; + +import { useAuthAtom } from '~/atoms/useAuthAtom'; +import { PATH } from '~/constants/routes'; + +const PrivateRoute = () => { + const { isLogin } = useAuthAtom(); + + if (!isLogin() && !checkIsWebView()) { + return ; + } + + return ( + <> + + + + + + ); +}; + +export default PrivateRoute; diff --git a/apps/admin/src/vite-env.d.ts b/apps/admin/src/vite-env.d.ts index 5b4bbe4e..8a96873b 100644 --- a/apps/admin/src/vite-env.d.ts +++ b/apps/admin/src/vite-env.d.ts @@ -11,4 +11,5 @@ interface ImportMeta { interface Window { __ENABLE_E2E_MSW__?: boolean; + __E2E_MSW_READY__?: boolean; }