feat(blog): add file-based blog with dynamic slugs, MDX content and layout shell

- Introduced blog routing using Next.js App Router
- Implemented dynamic [slug] pages for blog posts
- Added MDX-based content loading via lib/posts
- Integrated shared TopBar layout with navigation
- Established clear content, lib and component separation
This commit is contained in:
PascalSchattenburg
2026-01-22 14:14:15 +01:00
parent b717952234
commit d147843c76
10412 changed files with 2475583 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import { type PlaywrightTestConfig } from '@playwright/test';
import type { NextOptionsConfig } from './next-options';
/**
* This is the default configuration generated by Playwright as of v1.43.0 with some modifications.
*
* - the `testMatch` property is configured to match all `*.spec.js` or `*.spec.ts` files within the `app` and `pages` directories
* - the `use` property is configured with a baseURL matching the expected dev server endpoint (http://127.0.0.1:3000)
* - the `webserver` property is configured to run `next dev`.
*/
export declare const defaultPlaywrightConfig: PlaywrightTestConfig<NextOptionsConfig>;

View File

@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "defaultPlaywrightConfig", {
enumerable: true,
get: function() {
return defaultPlaywrightConfig;
}
});
const _test = require("@playwright/test");
const defaultPlaywrightConfig = {
testMatch: '{app,pages}/**/*.spec.{t,j}s',
fullyParallel: true,
forbidOnly: process.env.CI === 'true',
retries: process.env.CI === 'true' ? 2 : 0,
reporter: [
[
'list'
],
[
'html',
{
open: 'never'
}
]
],
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry'
},
projects: [
{
name: 'chromium',
use: {
..._test.devices['Desktop Chrome']
}
},
{
name: 'firefox',
use: {
..._test.devices['Desktop Firefox']
}
},
{
name: 'webkit',
use: {
..._test.devices['Desktop Safari']
}
}
],
webServer: {
command: process.env.CI === 'true' ? 'next start' : 'next dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: process.env.CI !== 'true'
}
};
//# sourceMappingURL=default-config.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/default-config.ts"],"sourcesContent":["import { devices, type PlaywrightTestConfig } from '@playwright/test'\nimport type { NextOptionsConfig } from './next-options'\n\n/**\n * This is the default configuration generated by Playwright as of v1.43.0 with some modifications.\n *\n * - the `testMatch` property is configured to match all `*.spec.js` or `*.spec.ts` files within the `app` and `pages` directories\n * - the `use` property is configured with a baseURL matching the expected dev server endpoint (http://127.0.0.1:3000)\n * - the `webserver` property is configured to run `next dev`.\n */\nexport const defaultPlaywrightConfig: PlaywrightTestConfig<NextOptionsConfig> =\n {\n testMatch: '{app,pages}/**/*.spec.{t,j}s',\n fullyParallel: true,\n forbidOnly: process.env.CI === 'true',\n retries: process.env.CI === 'true' ? 2 : 0,\n reporter: [['list'], ['html', { open: 'never' }]],\n use: {\n baseURL: 'http://127.0.0.1:3000',\n trace: 'on-first-retry',\n },\n projects: [\n {\n name: 'chromium',\n use: { ...devices['Desktop Chrome'] },\n },\n\n {\n name: 'firefox',\n use: { ...devices['Desktop Firefox'] },\n },\n\n {\n name: 'webkit',\n use: { ...devices['Desktop Safari'] },\n },\n ],\n webServer: {\n command: process.env.CI === 'true' ? 'next start' : 'next dev',\n url: 'http://127.0.0.1:3000',\n reuseExistingServer: process.env.CI !== 'true',\n },\n }\n"],"names":["defaultPlaywrightConfig","testMatch","fullyParallel","forbidOnly","process","env","CI","retries","reporter","open","use","baseURL","trace","projects","name","devices","webServer","command","url","reuseExistingServer"],"mappings":";;;;+BAUaA;;;eAAAA;;;sBAVsC;AAU5C,MAAMA,0BACX;IACEC,WAAW;IACXC,eAAe;IACfC,YAAYC,QAAQC,GAAG,CAACC,EAAE,KAAK;IAC/BC,SAASH,QAAQC,GAAG,CAACC,EAAE,KAAK,SAAS,IAAI;IACzCE,UAAU;QAAC;YAAC;SAAO;QAAE;YAAC;YAAQ;gBAAEC,MAAM;YAAQ;SAAE;KAAC;IACjDC,KAAK;QACHC,SAAS;QACTC,OAAO;IACT;IACAC,UAAU;QACR;YACEC,MAAM;YACNJ,KAAK;gBAAE,GAAGK,aAAO,CAAC,iBAAiB;YAAC;QACtC;QAEA;YACED,MAAM;YACNJ,KAAK;gBAAE,GAAGK,aAAO,CAAC,kBAAkB;YAAC;QACvC;QAEA;YACED,MAAM;YACNJ,KAAK;gBAAE,GAAGK,aAAO,CAAC,iBAAiB;YAAC;QACtC;KACD;IACDC,WAAW;QACTC,SAASb,QAAQC,GAAG,CAACC,EAAE,KAAK,SAAS,eAAe;QACpDY,KAAK;QACLC,qBAAqBf,QAAQC,GAAG,CAACC,EAAE,KAAK;IAC1C;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,17 @@
import * as base from '@playwright/test';
import type { NextFixture } from './next-fixture';
import type { NextOptions, NextOptionsConfig } from './next-options';
import type { NextWorkerFixture } from './next-worker-fixture';
import { defaultPlaywrightConfig } from './default-config';
export { defaultPlaywrightConfig };
export * from '@playwright/test';
export declare function defineConfig<T extends NextOptionsConfig, W>(config: base.PlaywrightTestConfig<T, W>): base.PlaywrightTestConfig<T, W>;
export type { NextFixture, NextOptions };
export type { FetchHandlerResult } from '../proxy';
export declare const test: base.TestType<base.PlaywrightTestArgs & base.PlaywrightTestOptions & {
next: NextFixture;
nextOptions: NextOptions;
}, base.PlaywrightWorkerArgs & base.PlaywrightWorkerOptions & {
_nextWorker: NextWorkerFixture;
}>;
export default test;

View File

@@ -0,0 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
default: null,
defaultPlaywrightConfig: null,
defineConfig: null,
test: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
default: function() {
return _default;
},
defaultPlaywrightConfig: function() {
return _defaultconfig.defaultPlaywrightConfig;
},
defineConfig: function() {
return defineConfig;
},
test: function() {
return test;
}
});
0 && __export(require("@playwright/test"));
const _test = /*#__PURE__*/ _interop_require_wildcard(_export_star(require("@playwright/test"), exports));
const _nextworkerfixture = require("./next-worker-fixture");
const _nextfixture = require("./next-fixture");
const _defaultconfig = require("./default-config");
function _export_star(from, to) {
Object.keys(from).forEach(function(k) {
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
Object.defineProperty(to, k, {
enumerable: true,
get: function() {
return from[k];
}
});
}
});
return from;
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function(nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
return {
default: obj
};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {
__proto__: null
};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for(var key in obj){
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function defineConfig(config) {
if (config.webServer !== undefined) {
// Playwright doesn't merge the `webServer` field as we'd expect, so remove our default if the user specifies one.
const { webServer, ...partialDefaultPlaywrightConfig } = _defaultconfig.defaultPlaywrightConfig;
return _test.defineConfig(partialDefaultPlaywrightConfig, config);
} else {
return _test.defineConfig(_defaultconfig.defaultPlaywrightConfig, config);
}
}
const test = _test.test.extend({
nextOptions: [
{
fetchLoopback: false
},
{
option: true
}
],
_nextWorker: [
// eslint-disable-next-line no-empty-pattern
async ({}, use)=>{
await (0, _nextworkerfixture.applyNextWorkerFixture)(use);
},
{
scope: 'worker',
auto: true
}
],
next: async ({ nextOptions, _nextWorker, page }, use, testInfo)=>{
await (0, _nextfixture.applyNextFixture)(use, {
testInfo,
nextWorker: _nextWorker,
page,
nextOptions
});
}
});
const _default = test;
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/index.ts"],"sourcesContent":["import * as base from '@playwright/test'\nimport type { NextFixture } from './next-fixture'\nimport type { NextOptions, NextOptionsConfig } from './next-options'\nimport type { NextWorkerFixture } from './next-worker-fixture'\nimport { applyNextWorkerFixture } from './next-worker-fixture'\nimport { applyNextFixture } from './next-fixture'\nimport { defaultPlaywrightConfig } from './default-config'\n\nexport { defaultPlaywrightConfig }\n\nexport * from '@playwright/test'\n\n// Export this second so it overrides the one from `@playwright/test`\nexport function defineConfig<T extends NextOptionsConfig, W>(\n config: base.PlaywrightTestConfig<T, W>\n): base.PlaywrightTestConfig<T, W>\nexport function defineConfig<T extends NextOptionsConfig = NextOptionsConfig>(\n config: base.PlaywrightTestConfig<T>\n): base.PlaywrightTestConfig<T> {\n if (config.webServer !== undefined) {\n // Playwright doesn't merge the `webServer` field as we'd expect, so remove our default if the user specifies one.\n const { webServer, ...partialDefaultPlaywrightConfig } =\n defaultPlaywrightConfig as base.PlaywrightTestConfig<T>\n return base.defineConfig<T>(partialDefaultPlaywrightConfig, config)\n } else {\n return base.defineConfig<T>(\n defaultPlaywrightConfig as base.PlaywrightTestConfig<T>,\n config\n )\n }\n}\n\nexport type { NextFixture, NextOptions }\nexport type { FetchHandlerResult } from '../proxy'\n\nexport const test = base.test.extend<\n { next: NextFixture; nextOptions: NextOptions },\n { _nextWorker: NextWorkerFixture }\n>({\n nextOptions: [{ fetchLoopback: false }, { option: true }],\n\n _nextWorker: [\n // eslint-disable-next-line no-empty-pattern\n async ({}, use) => {\n await applyNextWorkerFixture(use)\n },\n { scope: 'worker', auto: true },\n ],\n\n next: async ({ nextOptions, _nextWorker, page }, use, testInfo) => {\n await applyNextFixture(use, {\n testInfo,\n nextWorker: _nextWorker,\n page,\n nextOptions,\n })\n },\n})\n\nexport default test\n"],"names":["defaultPlaywrightConfig","defineConfig","test","config","webServer","undefined","partialDefaultPlaywrightConfig","base","extend","nextOptions","fetchLoopback","option","_nextWorker","use","applyNextWorkerFixture","scope","auto","next","page","testInfo","applyNextFixture","nextWorker"],"mappings":";;;;;;;;;;;;;;;;;IA2DA,OAAmB;eAAnB;;IAnDSA,uBAAuB;eAAvBA,sCAAuB;;IAQhBC,YAAY;eAAZA;;IAmBHC,IAAI;eAAJA;;;;2EAnCS;mCAIiB;6BACN;+BACO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUjC,SAASD,aACdE,MAAoC;IAEpC,IAAIA,OAAOC,SAAS,KAAKC,WAAW;QAClC,kHAAkH;QAClH,MAAM,EAAED,SAAS,EAAE,GAAGE,gCAAgC,GACpDN,sCAAuB;QACzB,OAAOO,MAAKN,YAAY,CAAIK,gCAAgCH;IAC9D,OAAO;QACL,OAAOI,MAAKN,YAAY,CACtBD,sCAAuB,EACvBG;IAEJ;AACF;AAKO,MAAMD,OAAOK,MAAKL,IAAI,CAACM,MAAM,CAGlC;IACAC,aAAa;QAAC;YAAEC,eAAe;QAAM;QAAG;YAAEC,QAAQ;QAAK;KAAE;IAEzDC,aAAa;QACX,4CAA4C;QAC5C,OAAO,EAAE,EAAEC;YACT,MAAMC,IAAAA,yCAAsB,EAACD;QAC/B;QACA;YAAEE,OAAO;YAAUC,MAAM;QAAK;KAC/B;IAEDC,MAAM,OAAO,EAAER,WAAW,EAAEG,WAAW,EAAEM,IAAI,EAAE,EAAEL,KAAKM;QACpD,MAAMC,IAAAA,6BAAgB,EAACP,KAAK;YAC1BM;YACAE,YAAYT;YACZM;YACAT;QACF;IACF;AACF;MAEA,WAAeP","ignoreList":[0]}

View File

@@ -0,0 +1,20 @@
import { defineConfig } from './index';
import type { NextFixture } from './next-fixture';
import { type RequestHandler } from 'msw';
export * from 'msw';
export * from '@playwright/test';
export type { NextFixture };
export { defineConfig };
export interface MswFixture {
use: (...handlers: RequestHandler[]) => void;
}
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
next: NextFixture;
nextOptions: import("./next-options").NextOptions;
} & {
msw: MswFixture;
mswHandlers: RequestHandler[];
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & {
_nextWorker: import("./next-worker-fixture").NextWorkerFixture;
}>;
export default test;

View File

@@ -0,0 +1,100 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
default: null,
defineConfig: null,
test: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
default: function() {
return _default;
},
defineConfig: function() {
return _index.defineConfig;
},
test: function() {
return test;
}
});
0 && __export(require("msw")) && __export(require("@playwright/test"));
const _index = require("./index");
const _msw = _export_star(require("msw"), exports);
const _stricteventemitter = require("strict-event-emitter");
_export_star(require("@playwright/test"), exports);
function _export_star(from, to) {
Object.keys(from).forEach(function(k) {
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
Object.defineProperty(to, k, {
enumerable: true,
get: function() {
return from[k];
}
});
}
});
return from;
}
const test = _index.test.extend({
mswHandlers: [
[],
{
option: true
}
],
msw: [
async ({ next, mswHandlers }, use)=>{
const handlers = [
...mswHandlers
];
const emitter = new _stricteventemitter.Emitter();
next.onFetch(async (request)=>{
const requestId = Math.random().toString(16).slice(2);
let isUnhandled = false;
let isPassthrough = false;
let mockedResponse;
await (0, _msw.handleRequest)(request.clone(), requestId, handlers.slice(0), {
onUnhandledRequest: ()=>{
isUnhandled = true;
}
}, emitter, {
onPassthroughResponse: ()=>{
isPassthrough = true;
},
onMockedResponse: (r)=>{
mockedResponse = r;
}
});
if (isUnhandled) {
return undefined;
}
if (isPassthrough) {
return 'continue';
}
if (mockedResponse) {
return mockedResponse;
}
return 'abort';
});
await use({
use: (...newHandlers)=>{
handlers.unshift(...newHandlers);
}
});
handlers.length = 0;
},
{
auto: true
}
]
});
const _default = test;
//# sourceMappingURL=msw.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/msw.ts"],"sourcesContent":["import { test as base, defineConfig } from './index'\nimport type { NextFixture } from './next-fixture'\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport { type RequestHandler, handleRequest } from 'msw'\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport { Emitter } from 'strict-event-emitter'\n\n// eslint-disable-next-line import/no-extraneous-dependencies\nexport * from 'msw'\nexport * from '@playwright/test'\nexport type { NextFixture }\nexport { defineConfig }\n\nexport interface MswFixture {\n use: (...handlers: RequestHandler[]) => void\n}\n\nexport const test = base.extend<{\n msw: MswFixture\n mswHandlers: RequestHandler[]\n}>({\n mswHandlers: [[], { option: true }],\n\n msw: [\n async ({ next, mswHandlers }, use) => {\n const handlers: RequestHandler[] = [...mswHandlers]\n const emitter = new Emitter()\n\n next.onFetch(async (request) => {\n const requestId = Math.random().toString(16).slice(2)\n let isUnhandled = false\n let isPassthrough = false\n let mockedResponse\n await handleRequest(\n request.clone(),\n requestId,\n handlers.slice(0),\n {\n onUnhandledRequest: () => {\n isUnhandled = true\n },\n },\n emitter as any,\n {\n onPassthroughResponse: () => {\n isPassthrough = true\n },\n onMockedResponse: (r) => {\n mockedResponse = r\n },\n }\n )\n\n if (isUnhandled) {\n return undefined\n }\n if (isPassthrough) {\n return 'continue'\n }\n\n if (mockedResponse) {\n return mockedResponse\n }\n\n return 'abort'\n })\n\n await use({\n use: (...newHandlers) => {\n handlers.unshift(...newHandlers)\n },\n })\n\n handlers.length = 0\n },\n { auto: true },\n ],\n})\n\nexport default test\n"],"names":["defineConfig","test","base","extend","mswHandlers","option","msw","next","use","handlers","emitter","Emitter","onFetch","request","requestId","Math","random","toString","slice","isUnhandled","isPassthrough","mockedResponse","handleRequest","clone","onUnhandledRequest","onPassthroughResponse","onMockedResponse","r","undefined","newHandlers","unshift","length","auto"],"mappings":";;;;;;;;;;;;;;;;IA+EA,OAAmB;eAAnB;;IApESA,YAAY;eAAZA,mBAAY;;IAMRC,IAAI;eAAJA;;;;uBAjB8B;kCAGQ;oCAE3B;qBAIV;;;;;;;;;;;;;;AAQP,MAAMA,OAAOC,WAAI,CAACC,MAAM,CAG5B;IACDC,aAAa;QAAC,EAAE;QAAE;YAAEC,QAAQ;QAAK;KAAE;IAEnCC,KAAK;QACH,OAAO,EAAEC,IAAI,EAAEH,WAAW,EAAE,EAAEI;YAC5B,MAAMC,WAA6B;mBAAIL;aAAY;YACnD,MAAMM,UAAU,IAAIC,2BAAO;YAE3BJ,KAAKK,OAAO,CAAC,OAAOC;gBAClB,MAAMC,YAAYC,KAAKC,MAAM,GAAGC,QAAQ,CAAC,IAAIC,KAAK,CAAC;gBACnD,IAAIC,cAAc;gBAClB,IAAIC,gBAAgB;gBACpB,IAAIC;gBACJ,MAAMC,IAAAA,kBAAa,EACjBT,QAAQU,KAAK,IACbT,WACAL,SAASS,KAAK,CAAC,IACf;oBACEM,oBAAoB;wBAClBL,cAAc;oBAChB;gBACF,GACAT,SACA;oBACEe,uBAAuB;wBACrBL,gBAAgB;oBAClB;oBACAM,kBAAkB,CAACC;wBACjBN,iBAAiBM;oBACnB;gBACF;gBAGF,IAAIR,aAAa;oBACf,OAAOS;gBACT;gBACA,IAAIR,eAAe;oBACjB,OAAO;gBACT;gBAEA,IAAIC,gBAAgB;oBAClB,OAAOA;gBACT;gBAEA,OAAO;YACT;YAEA,MAAMb,IAAI;gBACRA,KAAK,CAAC,GAAGqB;oBACPpB,SAASqB,OAAO,IAAID;gBACtB;YACF;YAEApB,SAASsB,MAAM,GAAG;QACpB;QACA;YAAEC,MAAM;QAAK;KACd;AACH;MAEA,WAAe/B","ignoreList":[0]}

View File

@@ -0,0 +1,12 @@
import type { Page, TestInfo } from '@playwright/test';
import type { NextWorkerFixture, FetchHandler } from './next-worker-fixture';
import type { NextOptions } from './next-options';
export interface NextFixture {
onFetch: (handler: FetchHandler) => void;
}
export declare function applyNextFixture(use: (fixture: NextFixture) => Promise<void>, { testInfo, nextOptions, nextWorker, page, }: {
testInfo: TestInfo;
nextOptions: NextOptions;
nextWorker: NextWorkerFixture;
page: Page;
}): Promise<void>;

View File

@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "applyNextFixture", {
enumerable: true,
get: function() {
return applyNextFixture;
}
});
const _pageroute = require("./page-route");
const _report = require("./report");
class NextFixtureImpl {
constructor(testInfo, options, worker, page){
this.testInfo = testInfo;
this.options = options;
this.worker = worker;
this.page = page;
this.fetchHandlers = [];
this.testId = testInfo.testId;
worker.onFetch(this.testId, this.handleFetch.bind(this));
}
async setup() {
const testHeaders = {
'Next-Test-Proxy-Port': String(this.worker.proxyPort),
'Next-Test-Data': this.testId
};
await this.page.context().route('**', (route)=>(0, _pageroute.handleRoute)(route, this.page, testHeaders, this.handleFetch.bind(this)));
}
teardown() {
this.worker.cleanupTest(this.testId);
}
onFetch(handler) {
this.fetchHandlers.push(handler);
}
async handleFetch(request) {
return (0, _report.reportFetch)(this.testInfo, request, async (req)=>{
for (const handler of this.fetchHandlers.slice().reverse()){
const result = await handler(req.clone());
if (result) {
return result;
}
}
if (this.options.fetchLoopback) {
return fetch(req.clone());
}
return undefined;
});
}
}
async function applyNextFixture(use, { testInfo, nextOptions, nextWorker, page }) {
const fixture = new NextFixtureImpl(testInfo, nextOptions, nextWorker, page);
await fixture.setup();
// eslint-disable-next-line react-hooks/rules-of-hooks -- not React.use()
await use(fixture);
fixture.teardown();
}
//# sourceMappingURL=next-fixture.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/next-fixture.ts"],"sourcesContent":["import type { Page, TestInfo } from '@playwright/test'\nimport type { NextWorkerFixture, FetchHandler } from './next-worker-fixture'\nimport type { NextOptions } from './next-options'\nimport type { FetchHandlerResult } from '../proxy'\nimport { handleRoute } from './page-route'\nimport { reportFetch } from './report'\n\nexport interface NextFixture {\n onFetch: (handler: FetchHandler) => void\n}\n\nclass NextFixtureImpl implements NextFixture {\n public readonly testId: string\n private fetchHandlers: FetchHandler[] = []\n\n constructor(\n private testInfo: TestInfo,\n private options: NextOptions,\n private worker: NextWorkerFixture,\n private page: Page\n ) {\n this.testId = testInfo.testId\n worker.onFetch(this.testId, this.handleFetch.bind(this))\n }\n\n async setup(): Promise<void> {\n const testHeaders = {\n 'Next-Test-Proxy-Port': String(this.worker.proxyPort),\n 'Next-Test-Data': this.testId,\n }\n\n await this.page\n .context()\n .route('**', (route) =>\n handleRoute(route, this.page, testHeaders, this.handleFetch.bind(this))\n )\n }\n\n teardown(): void {\n this.worker.cleanupTest(this.testId)\n }\n\n onFetch(handler: FetchHandler): void {\n this.fetchHandlers.push(handler)\n }\n\n private async handleFetch(request: Request): Promise<FetchHandlerResult> {\n return reportFetch(this.testInfo, request, async (req) => {\n for (const handler of this.fetchHandlers.slice().reverse()) {\n const result = await handler(req.clone())\n if (result) {\n return result\n }\n }\n if (this.options.fetchLoopback) {\n return fetch(req.clone())\n }\n return undefined\n })\n }\n}\n\nexport async function applyNextFixture(\n use: (fixture: NextFixture) => Promise<void>,\n {\n testInfo,\n nextOptions,\n nextWorker,\n page,\n }: {\n testInfo: TestInfo\n nextOptions: NextOptions\n nextWorker: NextWorkerFixture\n page: Page\n }\n): Promise<void> {\n const fixture = new NextFixtureImpl(testInfo, nextOptions, nextWorker, page)\n\n await fixture.setup()\n // eslint-disable-next-line react-hooks/rules-of-hooks -- not React.use()\n await use(fixture)\n\n fixture.teardown()\n}\n"],"names":["applyNextFixture","NextFixtureImpl","constructor","testInfo","options","worker","page","fetchHandlers","testId","onFetch","handleFetch","bind","setup","testHeaders","String","proxyPort","context","route","handleRoute","teardown","cleanupTest","handler","push","request","reportFetch","req","slice","reverse","result","clone","fetchLoopback","fetch","undefined","use","nextOptions","nextWorker","fixture"],"mappings":";;;;+BA8DsBA;;;eAAAA;;;2BA1DM;wBACA;AAM5B,MAAMC;IAIJC,YACE,AAAQC,QAAkB,EAC1B,AAAQC,OAAoB,EAC5B,AAAQC,MAAyB,EACjC,AAAQC,IAAU,CAClB;aAJQH,WAAAA;aACAC,UAAAA;aACAC,SAAAA;aACAC,OAAAA;aANFC,gBAAgC,EAAE;QAQxC,IAAI,CAACC,MAAM,GAAGL,SAASK,MAAM;QAC7BH,OAAOI,OAAO,CAAC,IAAI,CAACD,MAAM,EAAE,IAAI,CAACE,WAAW,CAACC,IAAI,CAAC,IAAI;IACxD;IAEA,MAAMC,QAAuB;QAC3B,MAAMC,cAAc;YAClB,wBAAwBC,OAAO,IAAI,CAACT,MAAM,CAACU,SAAS;YACpD,kBAAkB,IAAI,CAACP,MAAM;QAC/B;QAEA,MAAM,IAAI,CAACF,IAAI,CACZU,OAAO,GACPC,KAAK,CAAC,MAAM,CAACA,QACZC,IAAAA,sBAAW,EAACD,OAAO,IAAI,CAACX,IAAI,EAAEO,aAAa,IAAI,CAACH,WAAW,CAACC,IAAI,CAAC,IAAI;IAE3E;IAEAQ,WAAiB;QACf,IAAI,CAACd,MAAM,CAACe,WAAW,CAAC,IAAI,CAACZ,MAAM;IACrC;IAEAC,QAAQY,OAAqB,EAAQ;QACnC,IAAI,CAACd,aAAa,CAACe,IAAI,CAACD;IAC1B;IAEA,MAAcX,YAAYa,OAAgB,EAA+B;QACvE,OAAOC,IAAAA,mBAAW,EAAC,IAAI,CAACrB,QAAQ,EAAEoB,SAAS,OAAOE;YAChD,KAAK,MAAMJ,WAAW,IAAI,CAACd,aAAa,CAACmB,KAAK,GAAGC,OAAO,GAAI;gBAC1D,MAAMC,SAAS,MAAMP,QAAQI,IAAII,KAAK;gBACtC,IAAID,QAAQ;oBACV,OAAOA;gBACT;YACF;YACA,IAAI,IAAI,CAACxB,OAAO,CAAC0B,aAAa,EAAE;gBAC9B,OAAOC,MAAMN,IAAII,KAAK;YACxB;YACA,OAAOG;QACT;IACF;AACF;AAEO,eAAehC,iBACpBiC,GAA4C,EAC5C,EACE9B,QAAQ,EACR+B,WAAW,EACXC,UAAU,EACV7B,IAAI,EAML;IAED,MAAM8B,UAAU,IAAInC,gBAAgBE,UAAU+B,aAAaC,YAAY7B;IAEvE,MAAM8B,QAAQxB,KAAK;IACnB,yEAAyE;IACzE,MAAMqB,IAAIG;IAEVA,QAAQjB,QAAQ;AAClB","ignoreList":[0]}

View File

@@ -0,0 +1,6 @@
export interface NextOptions {
fetchLoopback?: boolean;
}
export interface NextOptionsConfig {
nextOptions?: NextOptions;
}

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
//# sourceMappingURL=next-options.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","ignoreList":[]}

View File

@@ -0,0 +1,8 @@
import type { FetchHandlerResult } from '../proxy';
export type FetchHandler = (request: Request) => FetchHandlerResult | Promise<FetchHandlerResult>;
export interface NextWorkerFixture {
proxyPort: number;
onFetch: (testId: string, handler: FetchHandler) => void;
cleanupTest: (testId: string) => void;
}
export declare function applyNextWorkerFixture(use: (fixture: NextWorkerFixture) => Promise<void>): Promise<void>;

View File

@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "applyNextWorkerFixture", {
enumerable: true,
get: function() {
return applyNextWorkerFixture;
}
});
const _proxy = require("../proxy");
class NextWorkerFixtureImpl {
async setup() {
const server = await (0, _proxy.createProxyServer)({
onFetch: this.handleProxyFetch.bind(this)
});
this.proxyPort = server.port;
this.proxyServer = server;
}
teardown() {
if (this.proxyServer) {
this.proxyServer.close();
this.proxyServer = null;
}
}
cleanupTest(testId) {
this.proxyFetchMap.delete(testId);
}
onFetch(testId, handler) {
this.proxyFetchMap.set(testId, handler);
}
async handleProxyFetch(testId, request) {
const handler = this.proxyFetchMap.get(testId);
return handler == null ? void 0 : handler(request);
}
constructor(){
this.proxyPort = 0;
this.proxyServer = null;
this.proxyFetchMap = new Map();
}
}
async function applyNextWorkerFixture(use) {
const fixture = new NextWorkerFixtureImpl();
await fixture.setup();
// eslint-disable-next-line react-hooks/rules-of-hooks -- not React.use()
await use(fixture);
fixture.teardown();
}
//# sourceMappingURL=next-worker-fixture.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/next-worker-fixture.ts"],"sourcesContent":["import type { FetchHandlerResult, ProxyServer } from '../proxy'\nimport { createProxyServer } from '../proxy'\n\nexport type FetchHandler = (\n request: Request\n) => FetchHandlerResult | Promise<FetchHandlerResult>\n\nexport interface NextWorkerFixture {\n proxyPort: number\n onFetch: (testId: string, handler: FetchHandler) => void\n cleanupTest: (testId: string) => void\n}\n\nclass NextWorkerFixtureImpl implements NextWorkerFixture {\n public proxyPort: number = 0\n private proxyServer: ProxyServer | null = null\n private proxyFetchMap = new Map<string, FetchHandler>()\n\n async setup(): Promise<void> {\n const server = await createProxyServer({\n onFetch: this.handleProxyFetch.bind(this),\n })\n\n this.proxyPort = server.port\n this.proxyServer = server\n }\n\n teardown(): void {\n if (this.proxyServer) {\n this.proxyServer.close()\n this.proxyServer = null\n }\n }\n\n cleanupTest(testId: string): void {\n this.proxyFetchMap.delete(testId)\n }\n\n onFetch(testId: string, handler: FetchHandler): void {\n this.proxyFetchMap.set(testId, handler)\n }\n\n private async handleProxyFetch(\n testId: string,\n request: Request\n ): Promise<FetchHandlerResult> {\n const handler = this.proxyFetchMap.get(testId)\n return handler?.(request)\n }\n}\n\nexport async function applyNextWorkerFixture(\n use: (fixture: NextWorkerFixture) => Promise<void>\n): Promise<void> {\n const fixture = new NextWorkerFixtureImpl()\n await fixture.setup()\n // eslint-disable-next-line react-hooks/rules-of-hooks -- not React.use()\n await use(fixture)\n fixture.teardown()\n}\n"],"names":["applyNextWorkerFixture","NextWorkerFixtureImpl","setup","server","createProxyServer","onFetch","handleProxyFetch","bind","proxyPort","port","proxyServer","teardown","close","cleanupTest","testId","proxyFetchMap","delete","handler","set","request","get","Map","use","fixture"],"mappings":";;;;+BAmDsBA;;;eAAAA;;;uBAlDY;AAYlC,MAAMC;IAKJ,MAAMC,QAAuB;QAC3B,MAAMC,SAAS,MAAMC,IAAAA,wBAAiB,EAAC;YACrCC,SAAS,IAAI,CAACC,gBAAgB,CAACC,IAAI,CAAC,IAAI;QAC1C;QAEA,IAAI,CAACC,SAAS,GAAGL,OAAOM,IAAI;QAC5B,IAAI,CAACC,WAAW,GAAGP;IACrB;IAEAQ,WAAiB;QACf,IAAI,IAAI,CAACD,WAAW,EAAE;YACpB,IAAI,CAACA,WAAW,CAACE,KAAK;YACtB,IAAI,CAACF,WAAW,GAAG;QACrB;IACF;IAEAG,YAAYC,MAAc,EAAQ;QAChC,IAAI,CAACC,aAAa,CAACC,MAAM,CAACF;IAC5B;IAEAT,QAAQS,MAAc,EAAEG,OAAqB,EAAQ;QACnD,IAAI,CAACF,aAAa,CAACG,GAAG,CAACJ,QAAQG;IACjC;IAEA,MAAcX,iBACZQ,MAAc,EACdK,OAAgB,EACa;QAC7B,MAAMF,UAAU,IAAI,CAACF,aAAa,CAACK,GAAG,CAACN;QACvC,OAAOG,2BAAAA,QAAUE;IACnB;;aAlCOX,YAAoB;aACnBE,cAAkC;aAClCK,gBAAgB,IAAIM;;AAiC9B;AAEO,eAAerB,uBACpBsB,GAAkD;IAElD,MAAMC,UAAU,IAAItB;IACpB,MAAMsB,QAAQrB,KAAK;IACnB,yEAAyE;IACzE,MAAMoB,IAAIC;IACVA,QAAQZ,QAAQ;AAClB","ignoreList":[0]}

View File

@@ -0,0 +1,3 @@
import type { Page, Route } from '@playwright/test';
import type { FetchHandler } from './next-worker-fixture';
export declare function handleRoute(route: Route, page: Page, testHeaders: Record<string, string>, fetchHandler: FetchHandler | null): Promise<void>;

View File

@@ -0,0 +1,60 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "handleRoute", {
enumerable: true,
get: function() {
return handleRoute;
}
});
function continueRoute(route, request, testHeaders) {
return route.continue({
headers: {
...request.headers(),
...testHeaders
}
});
}
async function handleRoute(route, page, testHeaders, fetchHandler) {
const request = route.request();
// Continue the navigation and non-fetch requests.
if (request.isNavigationRequest() || request.resourceType() !== 'fetch') {
return continueRoute(route, request, testHeaders);
}
// Continue the local requests. The followup requests will be intercepted
// on the server.
const pageOrigin = new URL(page.url()).origin;
const requestOrigin = new URL(request.url()).origin;
if (pageOrigin === requestOrigin) {
return continueRoute(route, request, testHeaders);
}
if (!fetchHandler) {
return route.abort();
}
const postData = request.postDataBuffer();
const fetchRequest = new Request(request.url(), {
method: request.method(),
headers: Object.fromEntries(Object.entries(request.headers()).filter(([name])=>!name.toLowerCase().startsWith('next-test-'))),
// @ts-expect-error - Type 'Buffer<ArrayBufferLike> | null' is not assignable to type 'BodyInit | null | undefined'.
body: postData ?? null
});
const proxyResponse = await fetchHandler(fetchRequest);
if (!proxyResponse) {
return route.abort();
}
if (proxyResponse === 'abort') {
return route.abort();
}
if (proxyResponse === 'continue') {
return continueRoute(route, request, testHeaders);
}
const { status, headers, body } = proxyResponse;
return route.fulfill({
status,
headers: Object.fromEntries(headers),
body: body ? Buffer.from(await proxyResponse.arrayBuffer()) : undefined
});
}
//# sourceMappingURL=page-route.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/page-route.ts"],"sourcesContent":["import type {\n Page,\n Route,\n Request as PlaywrightRequest,\n} from '@playwright/test'\nimport type { FetchHandler } from './next-worker-fixture'\n\nfunction continueRoute(\n route: Route,\n request: PlaywrightRequest,\n testHeaders: Record<string, string>\n): Promise<void> {\n return route.continue({\n headers: {\n ...request.headers(),\n ...testHeaders,\n },\n })\n}\n\nexport async function handleRoute(\n route: Route,\n page: Page,\n testHeaders: Record<string, string>,\n fetchHandler: FetchHandler | null\n) {\n const request = route.request()\n\n // Continue the navigation and non-fetch requests.\n if (request.isNavigationRequest() || request.resourceType() !== 'fetch') {\n return continueRoute(route, request, testHeaders)\n }\n\n // Continue the local requests. The followup requests will be intercepted\n // on the server.\n const pageOrigin = new URL(page.url()).origin\n const requestOrigin = new URL(request.url()).origin\n if (pageOrigin === requestOrigin) {\n return continueRoute(route, request, testHeaders)\n }\n\n if (!fetchHandler) {\n return route.abort()\n }\n\n const postData = request.postDataBuffer()\n const fetchRequest = new Request(request.url(), {\n method: request.method(),\n headers: Object.fromEntries(\n Object.entries(request.headers()).filter(\n ([name]) => !name.toLowerCase().startsWith('next-test-')\n )\n ),\n // @ts-expect-error - Type 'Buffer<ArrayBufferLike> | null' is not assignable to type 'BodyInit | null | undefined'.\n body: postData ?? null,\n })\n\n const proxyResponse = await fetchHandler(fetchRequest)\n if (!proxyResponse) {\n return route.abort()\n }\n if (proxyResponse === 'abort') {\n return route.abort()\n }\n if (proxyResponse === 'continue') {\n return continueRoute(route, request, testHeaders)\n }\n const { status, headers, body } = proxyResponse\n return route.fulfill({\n status,\n headers: Object.fromEntries(headers),\n body: body ? Buffer.from(await proxyResponse.arrayBuffer()) : undefined,\n })\n}\n"],"names":["handleRoute","continueRoute","route","request","testHeaders","continue","headers","page","fetchHandler","isNavigationRequest","resourceType","pageOrigin","URL","url","origin","requestOrigin","abort","postData","postDataBuffer","fetchRequest","Request","method","Object","fromEntries","entries","filter","name","toLowerCase","startsWith","body","proxyResponse","status","fulfill","Buffer","from","arrayBuffer","undefined"],"mappings":";;;;+BAoBsBA;;;eAAAA;;;AAbtB,SAASC,cACPC,KAAY,EACZC,OAA0B,EAC1BC,WAAmC;IAEnC,OAAOF,MAAMG,QAAQ,CAAC;QACpBC,SAAS;YACP,GAAGH,QAAQG,OAAO,EAAE;YACpB,GAAGF,WAAW;QAChB;IACF;AACF;AAEO,eAAeJ,YACpBE,KAAY,EACZK,IAAU,EACVH,WAAmC,EACnCI,YAAiC;IAEjC,MAAML,UAAUD,MAAMC,OAAO;IAE7B,kDAAkD;IAClD,IAAIA,QAAQM,mBAAmB,MAAMN,QAAQO,YAAY,OAAO,SAAS;QACvE,OAAOT,cAAcC,OAAOC,SAASC;IACvC;IAEA,yEAAyE;IACzE,iBAAiB;IACjB,MAAMO,aAAa,IAAIC,IAAIL,KAAKM,GAAG,IAAIC,MAAM;IAC7C,MAAMC,gBAAgB,IAAIH,IAAIT,QAAQU,GAAG,IAAIC,MAAM;IACnD,IAAIH,eAAeI,eAAe;QAChC,OAAOd,cAAcC,OAAOC,SAASC;IACvC;IAEA,IAAI,CAACI,cAAc;QACjB,OAAON,MAAMc,KAAK;IACpB;IAEA,MAAMC,WAAWd,QAAQe,cAAc;IACvC,MAAMC,eAAe,IAAIC,QAAQjB,QAAQU,GAAG,IAAI;QAC9CQ,QAAQlB,QAAQkB,MAAM;QACtBf,SAASgB,OAAOC,WAAW,CACzBD,OAAOE,OAAO,CAACrB,QAAQG,OAAO,IAAImB,MAAM,CACtC,CAAC,CAACC,KAAK,GAAK,CAACA,KAAKC,WAAW,GAAGC,UAAU,CAAC;QAG/C,oHAAoH;QACpHC,MAAMZ,YAAY;IACpB;IAEA,MAAMa,gBAAgB,MAAMtB,aAAaW;IACzC,IAAI,CAACW,eAAe;QAClB,OAAO5B,MAAMc,KAAK;IACpB;IACA,IAAIc,kBAAkB,SAAS;QAC7B,OAAO5B,MAAMc,KAAK;IACpB;IACA,IAAIc,kBAAkB,YAAY;QAChC,OAAO7B,cAAcC,OAAOC,SAASC;IACvC;IACA,MAAM,EAAE2B,MAAM,EAAEzB,OAAO,EAAEuB,IAAI,EAAE,GAAGC;IAClC,OAAO5B,MAAM8B,OAAO,CAAC;QACnBD;QACAzB,SAASgB,OAAOC,WAAW,CAACjB;QAC5BuB,MAAMA,OAAOI,OAAOC,IAAI,CAAC,MAAMJ,cAAcK,WAAW,MAAMC;IAChE;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,3 @@
import type { TestInfo } from '@playwright/test';
import type { FetchHandler } from './next-worker-fixture';
export declare function reportFetch(testInfo: TestInfo, req: Request, handler: FetchHandler): Promise<Awaited<ReturnType<FetchHandler>>>;

View File

@@ -0,0 +1,127 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "reportFetch", {
enumerable: true,
get: function() {
return reportFetch;
}
});
const _step = require("./step");
async function parseBody(r) {
const contentType = r.headers.get('content-type');
let error;
let text;
let json;
let formData;
let buffer;
if (contentType == null ? void 0 : contentType.includes('text')) {
try {
text = await r.text();
} catch (e) {
error = 'failed to parse text';
}
} else if (contentType == null ? void 0 : contentType.includes('json')) {
try {
json = await r.json();
} catch (e) {
error = 'failed to parse json';
}
} else if (contentType == null ? void 0 : contentType.includes('form-data')) {
try {
formData = await r.formData();
} catch (e) {
error = 'failed to parse formData';
}
} else {
try {
buffer = await r.arrayBuffer();
} catch (e) {
error = 'failed to parse arrayBuffer';
}
}
return {
...error ? {
error
} : null,
...text ? {
text
} : null,
...json ? {
json: JSON.stringify(json)
} : null,
...formData ? {
formData: JSON.stringify(Array.from(formData))
} : null,
...buffer && buffer.byteLength > 0 ? {
buffer: `base64;${Buffer.from(buffer).toString('base64')}`
} : null
};
}
function parseHeaders(headers) {
return Object.fromEntries(Array.from(headers).sort(([key1], [key2])=>key1.localeCompare(key2)).map(([key, value])=>{
return [
`header.${key}`,
value
];
}));
}
async function reportFetch(testInfo, req, handler) {
return (0, _step.step)(testInfo, {
title: `next.onFetch: ${req.method} ${req.url}`,
category: 'next.onFetch',
apiName: 'next.onFetch',
params: {
method: req.method,
url: req.url,
...await parseBody(req.clone()),
...parseHeaders(req.headers)
}
}, async (complete)=>{
const res = await handler(req);
if (res === undefined || res == null) {
complete({
error: {
message: 'unhandled'
}
});
} else if (typeof res === 'string' && res !== 'continue') {
complete({
error: {
message: res
}
});
} else {
let body;
if (typeof res === 'string') {
body = {
response: res
};
} else {
const { status, statusText } = res;
body = {
status,
...statusText ? {
statusText
} : null,
...await parseBody(res.clone()),
...parseHeaders(res.headers)
};
}
await (0, _step.step)(testInfo, {
title: `next.onFetch.fulfilled: ${req.method} ${req.url}`,
category: 'next.onFetch',
apiName: 'next.onFetch.fulfilled',
params: {
...body,
'request.url': req.url,
'request.method': req.method
}
}, async ()=>undefined).catch(()=>undefined);
}
return res;
});
}
//# sourceMappingURL=report.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
import type { TestInfo } from '@playwright/test';
export interface StepProps {
category: string;
title: string;
apiName?: string;
params?: Record<string, string | number | boolean | null | undefined>;
}
type Complete = (result: {
error?: any;
}) => void;
export declare function step<T>(_testInfo: TestInfo, props: StepProps, handler: (complete: Complete) => Promise<Awaited<T>>): Promise<Awaited<T>>;
export {};

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "step", {
enumerable: true,
get: function() {
return step;
}
});
const _test = require("@playwright/test");
async function step(_testInfo, props, handler) {
let result;
let reportedError;
try {
await _test.test.step(props.title, async ()=>{
result = await handler(({ error })=>{
reportedError = error;
if (reportedError) {
throw reportedError;
}
});
});
} catch (error) {
if (error !== reportedError) {
throw error;
}
}
return result;
}
//# sourceMappingURL=step.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testmode/playwright/step.ts"],"sourcesContent":["import type { TestInfo } from '@playwright/test'\nimport { test } from '@playwright/test'\n\nexport interface StepProps {\n category: string\n title: string\n apiName?: string\n params?: Record<string, string | number | boolean | null | undefined>\n}\n\ntype Complete = (result: { error?: any }) => void\n\nexport async function step<T>(\n _testInfo: TestInfo,\n props: StepProps,\n handler: (complete: Complete) => Promise<Awaited<T>>\n): Promise<Awaited<T>> {\n let result: Awaited<T>\n let reportedError: any\n try {\n await test.step(props.title, async () => {\n result = await handler(({ error }) => {\n reportedError = error\n if (reportedError) {\n throw reportedError\n }\n })\n })\n } catch (error) {\n if (error !== reportedError) {\n throw error\n }\n }\n return result!\n}\n"],"names":["step","_testInfo","props","handler","result","reportedError","test","title","error"],"mappings":";;;;+BAYsBA;;;eAAAA;;;sBAXD;AAWd,eAAeA,KACpBC,SAAmB,EACnBC,KAAgB,EAChBC,OAAoD;IAEpD,IAAIC;IACJ,IAAIC;IACJ,IAAI;QACF,MAAMC,UAAI,CAACN,IAAI,CAACE,MAAMK,KAAK,EAAE;YAC3BH,SAAS,MAAMD,QAAQ,CAAC,EAAEK,KAAK,EAAE;gBAC/BH,gBAAgBG;gBAChB,IAAIH,eAAe;oBACjB,MAAMA;gBACR;YACF;QACF;IACF,EAAE,OAAOG,OAAO;QACd,IAAIA,UAAUH,eAAe;YAC3B,MAAMG;QACR;IACF;IACA,OAAOJ;AACT","ignoreList":[0]}