@skyramp/mcp 0.3.2-rc.pom-3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +19 -13
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +201 -0
- package/build/index.js +80 -6
- package/build/prompts/code-reuse.js +3 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
- package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
- package/build/prompts/local-dev/local-dev-plan.js +429 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +190 -0
- package/build/prompts/pom-aware-code-reuse.js +12 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +37 -14
- package/build/prompts/sut-setup/shared.js +16 -12
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +24 -7
- package/build/prompts/test-recommendation/scopeAssessment.js +111 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +32 -52
- package/build/recommendation/planRanker.d.ts +15 -2
- package/build/recommendation/planRanker.js +76 -5
- package/build/resources/testbotResource.js +2 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +2 -1
- package/build/services/TestExecutionService.js +8 -3
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/services/TestGenerationService.js +39 -21
- package/build/services/containerEnv.js +3 -1
- package/build/tool-phases.js +6 -0
- package/build/tools/code-refactor/codeReuseTool.js +43 -4
- package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
- package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
- package/build/tools/code-refactor/reuse-outcome.js +158 -0
- package/build/tools/code-refactor/reuse-state.d.ts +45 -0
- package/build/tools/code-refactor/reuse-state.js +140 -0
- package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
- package/build/tools/enrichTestWithMocksTool.js +726 -0
- package/build/tools/executeSkyrampTestTool.d.ts +11 -0
- package/build/tools/executeSkyrampTestTool.js +62 -21
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
- package/build/tools/generate-tests/generateMockRestTool.js +391 -22
- package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
- package/build/tools/generate-tests/planGuard.js +2 -22
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
- package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
- package/build/tools/localDevWorkerComposeTool.js +264 -0
- package/build/tools/one-click/oneClickTool.d.ts +13 -0
- package/build/tools/one-click/oneClickTool.js +195 -24
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +96 -0
- package/build/tools/queryProxyMocksTool.d.ts +70 -0
- package/build/tools/queryProxyMocksTool.js +522 -0
- package/build/tools/runExistingTestsTool.d.ts +31 -0
- package/build/tools/runExistingTestsTool.js +214 -13
- package/build/tools/submitReportTool.js +38 -3
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +63 -40
- package/build/tools/test-management/registerTestPlanTool.js +55 -7
- package/build/tools/trace/startTraceCollectionTool.js +3 -3
- package/build/types/FrontendIntegration.d.ts +3 -0
- package/build/types/FrontendIntegration.js +3 -0
- package/build/types/OneClickCommands.d.ts +1 -1
- package/build/types/Recommendation.d.ts +20 -0
- package/build/types/Recommendation.js +32 -6
- package/build/types/RepositoryAnalysis.d.ts +131 -14
- package/build/types/RepositoryAnalysis.js +16 -2
- package/build/types/ReuseOutcome.d.ts +63 -0
- package/build/types/ReuseOutcome.js +33 -0
- package/build/types/TestExecution.d.ts +1 -0
- package/build/types/TestTypes.d.ts +25 -7
- package/build/types/TestTypes.js +22 -6
- package/build/types/TestbotReport.d.ts +7 -0
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +26 -0
- package/build/utils/AnalysisStateManager.js +31 -1
- package/build/utils/analyze-openapi.js +18 -1
- package/build/utils/branchDiff.d.ts +17 -1
- package/build/utils/branchDiff.js +99 -14
- package/build/utils/featureFlags.d.ts +31 -0
- package/build/utils/featureFlags.js +37 -0
- package/build/utils/frontendIntegration.js +8 -1
- package/build/utils/grpcMockValidation.d.ts +1 -0
- package/build/utils/grpcMockValidation.js +49 -0
- package/build/utils/httpMethodValidation.d.ts +4 -0
- package/build/utils/httpMethodValidation.js +15 -0
- package/build/utils/logger.js +1 -1
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +82 -0
- package/build/utils/pom-scope/pom-files.js +7 -0
- package/build/utils/pom-verify/bindings.js +11 -1
- package/build/utils/pom-verify/re-exports.d.ts +10 -0
- package/build/utils/pom-verify/re-exports.js +61 -0
- package/build/utils/pom-verify/resolve.d.ts +4 -1
- package/build/utils/pom-verify/resolve.js +7 -4
- package/build/utils/pom-verify/verify.d.ts +5 -0
- package/build/utils/pom-verify/verify.js +25 -3
- package/build/utils/progress.js +10 -5
- package/build/utils/proxy-terminal.js +3 -3
- package/build/utils/routeParsers.d.ts +3 -9
- package/build/utils/routeParsers.js +79 -4
- package/build/utils/utils.js +2 -2
- package/build/utils/versions.d.ts +4 -3
- package/build/utils/versions.js +3 -1
- package/build/utils/workspaceAuth.d.ts +46 -0
- package/build/utils/workspaceAuth.js +156 -1
- package/build/workspace/testSuites.d.ts +2 -1
- package/build/workspace/testSuites.js +1 -1
- package/build/workspace/workspace.d.ts +108 -32
- package/build/workspace/workspace.js +32 -4
- package/package.json +5 -2
- package/build/adapters/jestAdapter.test.d.ts +0 -1
- package/build/adapters/jestAdapter.test.js +0 -93
- package/build/adapters/mochaAdapter.test.d.ts +0 -1
- package/build/adapters/mochaAdapter.test.js +0 -63
- package/build/adapters/playwrightAdapter.test.d.ts +0 -1
- package/build/adapters/playwrightAdapter.test.js +0 -169
- package/build/adapters/pytestAdapter.test.d.ts +0 -1
- package/build/adapters/pytestAdapter.test.js +0 -90
- package/build/prompts/code-reuse.test.d.ts +0 -1
- package/build/prompts/code-reuse.test.js +0 -62
- package/build/prompts/pom-aware-code-reuse.test.d.ts +0 -1
- package/build/prompts/pom-aware-code-reuse.test.js +0 -11
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.d.ts +0 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +0 -51
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -264
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +0 -1
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +0 -126
- package/build/prompts/test-recommendation/promptPlan.test.d.ts +0 -1
- package/build/prompts/test-recommendation/promptPlan.test.js +0 -336
- package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +0 -1
- package/build/prompts/test-recommendation/scopeAssessment.test.js +0 -371
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +0 -1925
- package/build/prompts/testbot/testbot-prompts.test.d.ts +0 -1
- package/build/prompts/testbot/testbot-prompts.test.js +0 -451
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +0 -138
- package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/fixedNBudgeter.test.js +0 -66
- package/build/recommendation/discriminators.test.d.ts +0 -1
- package/build/recommendation/discriminators.test.js +0 -324
- package/build/recommendation/diversity.test.d.ts +0 -1
- package/build/recommendation/diversity.test.js +0 -77
- package/build/recommendation/planRanker.test.d.ts +0 -1
- package/build/recommendation/planRanker.test.js +0 -110
- package/build/resources/testbotResource.test.d.ts +0 -1
- package/build/resources/testbotResource.test.js +0 -44
- package/build/services/AnalyticsService.test.d.ts +0 -1
- package/build/services/AnalyticsService.test.js +0 -86
- package/build/services/ScenarioGenerationService.integration.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.integration.test.js +0 -162
- package/build/services/ScenarioGenerationService.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.test.js +0 -414
- package/build/services/TestDiscoveryService.test.d.ts +0 -1
- package/build/services/TestDiscoveryService.test.js +0 -983
- package/build/services/TestExecutionService.test.d.ts +0 -1
- package/build/services/TestExecutionService.test.js +0 -1032
- package/build/services/TestGenerationService.test.d.ts +0 -1
- package/build/services/TestGenerationService.test.js +0 -578
- package/build/tool-phase-coverage.test.d.ts +0 -1
- package/build/tool-phase-coverage.test.js +0 -49
- package/build/tools/code-refactor/codeReuseTool.test.d.ts +0 -1
- package/build/tools/code-refactor/codeReuseTool.test.js +0 -572
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +0 -433
- package/build/tools/generate-tests/generateContractRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateContractRestTool.test.js +0 -142
- package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.test.js +0 -159
- package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateLoadRestTool.test.js +0 -169
- package/build/tools/generate-tests/generateUIRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateUIRestTool.test.js +0 -32
- package/build/tools/generate-tests/planGuard.test.d.ts +0 -1
- package/build/tools/generate-tests/planGuard.test.js +0 -185
- package/build/tools/generate-tests/scenarioLint.test.d.ts +0 -1
- package/build/tools/generate-tests/scenarioLint.test.js +0 -100
- package/build/tools/runExistingTestsTool.test.d.ts +0 -1
- package/build/tools/runExistingTestsTool.test.js +0 -379
- package/build/tools/submitReportTool.test.d.ts +0 -1
- package/build/tools/submitReportTool.test.js +0 -1428
- package/build/tools/test-management/actionsTool.test.d.ts +0 -1
- package/build/tools/test-management/actionsTool.test.js +0 -436
- package/build/tools/test-management/analyzeChangesTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeChangesTool.test.js +0 -522
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeTestHealthTool.test.js +0 -385
- package/build/tools/test-management/registerTestPlanTool.test.d.ts +0 -1
- package/build/tools/test-management/registerTestPlanTool.test.js +0 -296
- package/build/tools/trace/resolveSaveStoragePath.test.d.ts +0 -1
- package/build/tools/trace/resolveSaveStoragePath.test.js +0 -17
- package/build/tools/trace/resolveSessionPaths.test.d.ts +0 -1
- package/build/tools/trace/resolveSessionPaths.test.js +0 -104
- package/build/tools/trace/sessionState.test.d.ts +0 -1
- package/build/tools/trace/sessionState.test.js +0 -17
- package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +0 -1
- package/build/tools/workspace/initializeWorkspaceTool.test.js +0 -139
- package/build/tools/workspace/serviceUpsert.test.d.ts +0 -1
- package/build/tools/workspace/serviceUpsert.test.js +0 -50
- package/build/utils/AnalysisStateManager.test.d.ts +0 -1
- package/build/utils/AnalysisStateManager.test.js +0 -134
- package/build/utils/dartRouteExtractor.test.d.ts +0 -1
- package/build/utils/dartRouteExtractor.test.js +0 -307
- package/build/utils/docker.test.d.ts +0 -1
- package/build/utils/docker.test.js +0 -114
- package/build/utils/featureFlags.test.d.ts +0 -1
- package/build/utils/featureFlags.test.js +0 -81
- package/build/utils/fileWalk.test.d.ts +0 -1
- package/build/utils/fileWalk.test.js +0 -252
- package/build/utils/frontendIntegration.test.d.ts +0 -1
- package/build/utils/frontendIntegration.test.js +0 -229
- package/build/utils/frontendSelectors.test.d.ts +0 -1
- package/build/utils/frontendSelectors.test.js +0 -118
- package/build/utils/gitStaging.test.d.ts +0 -1
- package/build/utils/gitStaging.test.js +0 -111
- package/build/utils/httpDefaults.test.d.ts +0 -1
- package/build/utils/httpDefaults.test.js +0 -21
- package/build/utils/importerHop.test.d.ts +0 -1
- package/build/utils/importerHop.test.js +0 -469
- package/build/utils/pathAffinityClassification.test.d.ts +0 -1
- package/build/utils/pathAffinityClassification.test.js +0 -208
- package/build/utils/planMatchKeys.test.d.ts +0 -1
- package/build/utils/planMatchKeys.test.js +0 -123
- package/build/utils/pom-scope/index.test.d.ts +0 -1
- package/build/utils/pom-scope/index.test.js +0 -278
- package/build/utils/pom-scope/pom-files.test.d.ts +0 -1
- package/build/utils/pom-scope/pom-files.test.js +0 -29
- package/build/utils/pom-scope/scoring.test.d.ts +0 -1
- package/build/utils/pom-scope/scoring.test.js +0 -39
- package/build/utils/pom-scope/selector-extractor.test.d.ts +0 -1
- package/build/utils/pom-scope/selector-extractor.test.js +0 -67
- package/build/utils/pom-verify/bindings.test.d.ts +0 -1
- package/build/utils/pom-verify/bindings.test.js +0 -164
- package/build/utils/pom-verify/calls.test.d.ts +0 -1
- package/build/utils/pom-verify/calls.test.js +0 -61
- package/build/utils/pom-verify/resolve.test.d.ts +0 -1
- package/build/utils/pom-verify/resolve.test.js +0 -114
- package/build/utils/pom-verify/verify.test.d.ts +0 -1
- package/build/utils/pom-verify/verify.test.js +0 -374
- package/build/utils/pr-comment-parser.test.d.ts +0 -1
- package/build/utils/pr-comment-parser.test.js +0 -428
- package/build/utils/progress.test.d.ts +0 -1
- package/build/utils/progress.test.js +0 -37
- package/build/utils/projectMetadata.test.d.ts +0 -1
- package/build/utils/projectMetadata.test.js +0 -172
- package/build/utils/pythonMountPrefixes.test.d.ts +0 -1
- package/build/utils/pythonMountPrefixes.test.js +0 -113
- package/build/utils/repoScanner.test.d.ts +0 -1
- package/build/utils/repoScanner.test.js +0 -190
- package/build/utils/reportVerification.test.d.ts +0 -1
- package/build/utils/reportVerification.test.js +0 -185
- package/build/utils/routeParsers.test.d.ts +0 -1
- package/build/utils/routeParsers.test.js +0 -1011
- package/build/utils/scenarioDrafting.test.d.ts +0 -1
- package/build/utils/scenarioDrafting.test.js +0 -876
- package/build/utils/sourceRouteExtractor.test.d.ts +0 -1
- package/build/utils/sourceRouteExtractor.test.js +0 -738
- package/build/utils/telemetry.test.d.ts +0 -1
- package/build/utils/telemetry.test.js +0 -70
- package/build/utils/trace-parser.test.d.ts +0 -6
- package/build/utils/trace-parser.test.js +0 -140
- package/build/utils/uiPageEnumerator.test.d.ts +0 -1
- package/build/utils/uiPageEnumerator.test.js +0 -824
- package/build/utils/utils.test.d.ts +0 -1
- package/build/utils/utils.test.js +0 -103
- package/build/utils/walkerCharacterization.test.d.ts +0 -1
- package/build/utils/walkerCharacterization.test.js +0 -233
- package/build/utils/workspaceAuth.test.d.ts +0 -1
- package/build/utils/workspaceAuth.test.js +0 -260
- package/build/workspace/testSuites.test.d.ts +0 -1
- package/build/workspace/testSuites.test.js +0 -23
- package/build/workspace/workspace.test.d.ts +0 -1
- package/build/workspace/workspace.test.js +0 -264
|
@@ -1,824 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as os from "os";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
// Under native ESM, imported module namespaces are read-only, so jest.spyOn on
|
|
7
|
-
// workspaceAuth fails. Partial-mock the module: keep every real export the
|
|
8
|
-
// subject's import graph needs and override only readWorkspaceConfigRaw.
|
|
9
|
-
// jest.requireActual can't load an ESM module synchronously, so import the real
|
|
10
|
-
// module first (unstable_mockModule is not hoisted) and spread its namespace.
|
|
11
|
-
const realWorkspaceAuth = await import("./workspaceAuth.js");
|
|
12
|
-
const readWorkspaceConfigRaw = jest.fn();
|
|
13
|
-
jest.unstable_mockModule("./workspaceAuth.js", () => ({
|
|
14
|
-
...realWorkspaceAuth,
|
|
15
|
-
readWorkspaceConfigRaw,
|
|
16
|
-
}));
|
|
17
|
-
const { frameworkFileToUrlPath, findCandidatePagesByFrameworkRoute, findCandidatePagesBySourceRoute, findRootFallbackPage, enumerateCandidateUiPages, pickFrontendBaseUrl, detectsFilesystemRouting, } = await import("./uiPageEnumerator.js");
|
|
18
|
-
// Helper to create a tmp repo dir with optional config files; returns the path.
|
|
19
|
-
function makeTmpRepo(configFiles = []) {
|
|
20
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-test-repo-"));
|
|
21
|
-
for (const cfg of configFiles) {
|
|
22
|
-
const fullPath = path.join(dir, cfg);
|
|
23
|
-
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
24
|
-
fs.writeFileSync(fullPath, "");
|
|
25
|
-
}
|
|
26
|
-
return dir;
|
|
27
|
-
}
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// frameworkFileToUrlPath
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
describe("frameworkFileToUrlPath", () => {
|
|
32
|
-
it("maps Next.js App Router page.tsx to /path", () => {
|
|
33
|
-
expect(frameworkFileToUrlPath("app/orders/page.tsx")).toBe("/orders");
|
|
34
|
-
});
|
|
35
|
-
it("maps Next.js App Router root page.tsx to /", () => {
|
|
36
|
-
expect(frameworkFileToUrlPath("app/page.tsx")).toBe("/");
|
|
37
|
-
});
|
|
38
|
-
it("maps Next.js App Router dynamic [id] segment to :id", () => {
|
|
39
|
-
expect(frameworkFileToUrlPath("app/orders/[id]/page.tsx")).toBe("/orders/:id");
|
|
40
|
-
});
|
|
41
|
-
it("strips Next.js App Router route groups (parentheses)", () => {
|
|
42
|
-
expect(frameworkFileToUrlPath("app/(marketing)/about/page.tsx")).toBe("/about");
|
|
43
|
-
});
|
|
44
|
-
it("maps Next.js Pages Router /pages/foo.tsx to /foo", () => {
|
|
45
|
-
expect(frameworkFileToUrlPath("pages/orders.tsx")).toBe("/orders");
|
|
46
|
-
});
|
|
47
|
-
it("maps Next.js Pages Router /pages/index.tsx to /", () => {
|
|
48
|
-
expect(frameworkFileToUrlPath("pages/index.tsx")).toBe("/");
|
|
49
|
-
});
|
|
50
|
-
it("maps Next.js Pages Router /pages/foo/index.tsx to /foo", () => {
|
|
51
|
-
expect(frameworkFileToUrlPath("pages/orders/index.tsx")).toBe("/orders");
|
|
52
|
-
});
|
|
53
|
-
it("excludes Next.js Pages Router API handlers (/pages/api/*)", () => {
|
|
54
|
-
expect(frameworkFileToUrlPath("pages/api/orders.ts")).toBeNull();
|
|
55
|
-
});
|
|
56
|
-
it("maps Vue / Nuxt /pages/foo.vue to /foo", () => {
|
|
57
|
-
expect(frameworkFileToUrlPath("pages/orders.vue")).toBe("/orders");
|
|
58
|
-
});
|
|
59
|
-
it("maps Vue / Nuxt 2 /pages/_id.vue to :id", () => {
|
|
60
|
-
expect(frameworkFileToUrlPath("pages/_id.vue")).toBe("/:id");
|
|
61
|
-
});
|
|
62
|
-
it("maps SvelteKit routes/+page.svelte to /", () => {
|
|
63
|
-
expect(frameworkFileToUrlPath("src/routes/+page.svelte")).toBe("/");
|
|
64
|
-
});
|
|
65
|
-
it("maps SvelteKit routes/foo/+page.svelte to /foo", () => {
|
|
66
|
-
expect(frameworkFileToUrlPath("src/routes/orders/+page.svelte")).toBe("/orders");
|
|
67
|
-
});
|
|
68
|
-
it("returns null for a generic component file (not a route)", () => {
|
|
69
|
-
expect(frameworkFileToUrlPath("src/components/Button.tsx")).toBeNull();
|
|
70
|
-
});
|
|
71
|
-
it("returns null for a layout file in app/", () => {
|
|
72
|
-
expect(frameworkFileToUrlPath("app/orders/layout.tsx")).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
it("returns null for a styles file under frontend dir", () => {
|
|
75
|
-
expect(frameworkFileToUrlPath("src/styles/global.css")).toBeNull();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// findCandidatePagesByFrameworkRoute
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
describe("findCandidatePagesByFrameworkRoute", () => {
|
|
82
|
-
it("returns empty for empty input", () => {
|
|
83
|
-
expect(findCandidatePagesByFrameworkRoute([], "http://localhost:3000")).toEqual([]);
|
|
84
|
-
});
|
|
85
|
-
it("returns empty when no files map to routes", () => {
|
|
86
|
-
expect(findCandidatePagesByFrameworkRoute(["src/components/Button.tsx", "src/utils/format.ts"], "http://localhost:3000")).toEqual([]);
|
|
87
|
-
});
|
|
88
|
-
it("emits a single page with full URL for a Next.js App Router route", () => {
|
|
89
|
-
const pages = findCandidatePagesByFrameworkRoute(["app/orders/page.tsx"], "http://localhost:3000");
|
|
90
|
-
expect(pages).toEqual([
|
|
91
|
-
{
|
|
92
|
-
url: "http://localhost:3000/orders",
|
|
93
|
-
sourcedFrom: ["app/orders/page.tsx"],
|
|
94
|
-
strategy: "framework-route-grep",
|
|
95
|
-
},
|
|
96
|
-
]);
|
|
97
|
-
});
|
|
98
|
-
it("collapses two files emitting the same URL into one entry with both sources", () => {
|
|
99
|
-
const pages = findCandidatePagesByFrameworkRoute(["app/orders/page.tsx", "app/orders/page.tsx"], "http://localhost:3000");
|
|
100
|
-
expect(pages).toHaveLength(1);
|
|
101
|
-
expect(pages[0].sourcedFrom).toEqual(["app/orders/page.tsx"]);
|
|
102
|
-
});
|
|
103
|
-
it("strips trailing slash from baseUrl before joining", () => {
|
|
104
|
-
const pages = findCandidatePagesByFrameworkRoute(["app/orders/page.tsx"], "http://localhost:3000/");
|
|
105
|
-
expect(pages[0].url).toBe("http://localhost:3000/orders");
|
|
106
|
-
});
|
|
107
|
-
it("ignores files that don't map to routes (mixed input)", () => {
|
|
108
|
-
const pages = findCandidatePagesByFrameworkRoute([
|
|
109
|
-
"src/components/Button.tsx", // not a route
|
|
110
|
-
"app/orders/page.tsx", // route
|
|
111
|
-
"src/styles/main.css", // not a route
|
|
112
|
-
], "http://localhost:3000");
|
|
113
|
-
expect(pages).toHaveLength(1);
|
|
114
|
-
expect(pages[0].url).toBe("http://localhost:3000/orders");
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// findCandidatePagesBySourceRoute (strategy 2)
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
describe("findCandidatePagesBySourceRoute", () => {
|
|
121
|
-
function makeRouterRepo() {
|
|
122
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-strat2-"));
|
|
123
|
-
fs.mkdirSync(path.join(dir, "src", "pages"), { recursive: true });
|
|
124
|
-
fs.writeFileSync(path.join(dir, "src", "App.tsx"), `
|
|
125
|
-
import { Routes, Route } from 'react-router-dom';
|
|
126
|
-
import Cart from './pages/Cart';
|
|
127
|
-
import Orders from './pages/Orders';
|
|
128
|
-
|
|
129
|
-
export default function App() {
|
|
130
|
-
return (
|
|
131
|
-
<Routes>
|
|
132
|
-
<Route path="/cart" element={<Cart />} />
|
|
133
|
-
<Route path="/orders" element={<Orders />} />
|
|
134
|
-
</Routes>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
`);
|
|
138
|
-
fs.writeFileSync(path.join(dir, "src", "pages", "Cart.tsx"), "export default function Cart() { return <div/>; }");
|
|
139
|
-
fs.writeFileSync(path.join(dir, "src", "pages", "Orders.tsx"), "export default function Orders() { return <div/>; }");
|
|
140
|
-
return dir;
|
|
141
|
-
}
|
|
142
|
-
it("emits candidate pages for changed files that match a route's component", () => {
|
|
143
|
-
const repo = makeRouterRepo();
|
|
144
|
-
const pages = findCandidatePagesBySourceRoute(["src/pages/Cart.tsx"], "http://localhost:3000", repo);
|
|
145
|
-
expect(pages).toHaveLength(1);
|
|
146
|
-
expect(pages[0]).toMatchObject({
|
|
147
|
-
url: "http://localhost:3000/cart",
|
|
148
|
-
strategy: "source-grounded-routes",
|
|
149
|
-
});
|
|
150
|
-
expect(pages[0].sourcedFrom).toEqual(["src/pages/Cart.tsx"]);
|
|
151
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
152
|
-
});
|
|
153
|
-
it("emits multiple pages when multiple changed files match routes", () => {
|
|
154
|
-
const repo = makeRouterRepo();
|
|
155
|
-
const pages = findCandidatePagesBySourceRoute(["src/pages/Cart.tsx", "src/pages/Orders.tsx"], "http://localhost:3000", repo);
|
|
156
|
-
expect(pages.map((p) => p.url).sort()).toEqual([
|
|
157
|
-
"http://localhost:3000/cart",
|
|
158
|
-
"http://localhost:3000/orders",
|
|
159
|
-
]);
|
|
160
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
161
|
-
});
|
|
162
|
-
it("returns empty when no changed files match a route's component", () => {
|
|
163
|
-
const repo = makeRouterRepo();
|
|
164
|
-
const pages = findCandidatePagesBySourceRoute(["src/components/Button.tsx"], "http://localhost:3000", repo);
|
|
165
|
-
expect(pages).toEqual([]);
|
|
166
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
167
|
-
});
|
|
168
|
-
it("returns empty when the repo has no router files", () => {
|
|
169
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-empty-"));
|
|
170
|
-
const pages = findCandidatePagesBySourceRoute(["src/pages/Cart.tsx"], "http://localhost:3000", repo);
|
|
171
|
-
expect(pages).toEqual([]);
|
|
172
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
173
|
-
});
|
|
174
|
-
it("strips trailing slash from baseUrl", () => {
|
|
175
|
-
const repo = makeRouterRepo();
|
|
176
|
-
const pages = findCandidatePagesBySourceRoute(["src/pages/Cart.tsx"], "http://localhost:3000/", repo);
|
|
177
|
-
expect(pages[0].url).toBe("http://localhost:3000/cart");
|
|
178
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
179
|
-
});
|
|
180
|
-
it("emits candidate pages when the changed file is the route DECLARATION (router.tsx, not the component)", () => {
|
|
181
|
-
// Regression: previously we only matched route.componentFile. A PR that
|
|
182
|
-
// renames a route in App.tsx but doesn't touch the component itself would
|
|
183
|
-
// surface zero source-grounded candidates. Real example: directus P06
|
|
184
|
-
// renamed /settings/data-model in modules/settings/index.ts; the component
|
|
185
|
-
// was unchanged, so the agent fell through to root-fallback and missed
|
|
186
|
-
// the changed routes entirely.
|
|
187
|
-
const repo = makeRouterRepo();
|
|
188
|
-
const pages = findCandidatePagesBySourceRoute(["src/App.tsx"], "http://localhost:3000", repo);
|
|
189
|
-
expect(pages.map((p) => p.url).sort()).toEqual([
|
|
190
|
-
"http://localhost:3000/cart",
|
|
191
|
-
"http://localhost:3000/orders",
|
|
192
|
-
]);
|
|
193
|
-
expect(pages[0].sourcedFrom).toEqual(["src/App.tsx"]);
|
|
194
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
// pickFrontendBaseUrl
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
|
-
describe("pickFrontendBaseUrl", () => {
|
|
201
|
-
afterEach(() => {
|
|
202
|
-
readWorkspaceConfigRaw.mockReset();
|
|
203
|
-
});
|
|
204
|
-
it("returns null when workspace config is missing", async () => {
|
|
205
|
-
readWorkspaceConfigRaw.mockResolvedValue(null);
|
|
206
|
-
expect(await pickFrontendBaseUrl("/repo")).toBeNull();
|
|
207
|
-
});
|
|
208
|
-
it("returns null when no services have baseUrl", async () => {
|
|
209
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
210
|
-
services: [{ serviceName: "backend", language: "python" }],
|
|
211
|
-
});
|
|
212
|
-
expect(await pickFrontendBaseUrl("/repo")).toBeNull();
|
|
213
|
-
});
|
|
214
|
-
it("picks the frontend service by name suffix", async () => {
|
|
215
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
216
|
-
services: [
|
|
217
|
-
{ serviceName: "demoshop-backend", api: { baseUrl: "http://localhost:8000" } },
|
|
218
|
-
{ serviceName: "demoshop-frontend", api: { baseUrl: "http://localhost:5173" } },
|
|
219
|
-
],
|
|
220
|
-
});
|
|
221
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:5173");
|
|
222
|
-
});
|
|
223
|
-
it("picks the frontend service by framework when name doesn't signal it", async () => {
|
|
224
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
225
|
-
services: [
|
|
226
|
-
{ serviceName: "api", language: "python", api: { baseUrl: "http://localhost:8000" } },
|
|
227
|
-
{
|
|
228
|
-
serviceName: "client",
|
|
229
|
-
language: "typescript",
|
|
230
|
-
framework: "next",
|
|
231
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
});
|
|
235
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:3000");
|
|
236
|
-
});
|
|
237
|
-
it("falls back to first service with baseUrl when no frontend signal", async () => {
|
|
238
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
239
|
-
services: [
|
|
240
|
-
{ serviceName: "svc-a", language: "go", api: { baseUrl: "http://localhost:8000" } },
|
|
241
|
-
{ serviceName: "svc-b", language: "go", api: { baseUrl: "http://localhost:8001" } },
|
|
242
|
-
],
|
|
243
|
-
});
|
|
244
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8000");
|
|
245
|
-
});
|
|
246
|
-
// Flutter support — the load-bearing assumption from the Confluence plan.
|
|
247
|
-
// init_workspace writes a single service with framework: playwright for a
|
|
248
|
-
// Flutter web app. Heuristic 1 must match by serviceName suffix
|
|
249
|
-
// (e.g. "birdle-frontend") because Heuristic 2 only knows JS/TS frontend
|
|
250
|
-
// frameworks. Without this, the agent never reaches the browser regardless
|
|
251
|
-
// of the .dart classifier fix.
|
|
252
|
-
it("picks the Flutter playwright-framework service by name suffix", async () => {
|
|
253
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
254
|
-
services: [
|
|
255
|
-
{
|
|
256
|
-
serviceName: "birdle-frontend",
|
|
257
|
-
language: "typescript",
|
|
258
|
-
framework: "playwright",
|
|
259
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
});
|
|
263
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
|
|
264
|
-
});
|
|
265
|
-
// Edge case: Flutter service has no "frontend"/"ui"/"app"/"web" suffix
|
|
266
|
-
// (so H1 misses) and `framework: playwright` isn't in H2's TS/JS framework
|
|
267
|
-
// allow-list. The chosen URL must come from H3 (first service with baseUrl).
|
|
268
|
-
// We add a second service to prove H3 picked the first, not H1 by accident.
|
|
269
|
-
it("falls back to first service when name+framework don't match H1 or H2", async () => {
|
|
270
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
271
|
-
services: [
|
|
272
|
-
{
|
|
273
|
-
serviceName: "birdle",
|
|
274
|
-
language: "typescript",
|
|
275
|
-
framework: "playwright",
|
|
276
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
serviceName: "other-service",
|
|
280
|
-
language: "go",
|
|
281
|
-
framework: "gin",
|
|
282
|
-
api: { baseUrl: "http://localhost:9000" },
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
});
|
|
286
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
|
|
287
|
-
});
|
|
288
|
-
// SKYR-3812: Environment variable override tests
|
|
289
|
-
describe("env var overrides (SKYR-3812)", () => {
|
|
290
|
-
let originalGlobal;
|
|
291
|
-
let originalPerSvc = {};
|
|
292
|
-
beforeEach(() => {
|
|
293
|
-
// Snapshot env vars this suite may touch so we can restore them exactly.
|
|
294
|
-
originalGlobal = process.env.SKYRAMP_TEST_BASE_URL;
|
|
295
|
-
originalPerSvc = {};
|
|
296
|
-
for (const key of Object.keys(process.env)) {
|
|
297
|
-
if (key.startsWith("SKYRAMP_TEST_SERVICE_URL_")) {
|
|
298
|
-
originalPerSvc[key] = process.env[key];
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
afterEach(() => {
|
|
303
|
-
// Restore the global override.
|
|
304
|
-
if (originalGlobal === undefined) {
|
|
305
|
-
delete process.env.SKYRAMP_TEST_BASE_URL;
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
process.env.SKYRAMP_TEST_BASE_URL = originalGlobal;
|
|
309
|
-
}
|
|
310
|
-
// Restore per-service vars: reset the union of keys that existed before
|
|
311
|
-
// the test and keys the test set, so neither pre-existing values nor
|
|
312
|
-
// test-set values leak across tests or out of the suite.
|
|
313
|
-
const keys = new Set([
|
|
314
|
-
...Object.keys(originalPerSvc),
|
|
315
|
-
...Object.keys(process.env).filter((k) => k.startsWith("SKYRAMP_TEST_SERVICE_URL_")),
|
|
316
|
-
]);
|
|
317
|
-
for (const key of keys) {
|
|
318
|
-
if (originalPerSvc[key] === undefined) {
|
|
319
|
-
delete process.env[key];
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
process.env[key] = originalPerSvc[key];
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
readWorkspaceConfigRaw.mockReset();
|
|
326
|
-
});
|
|
327
|
-
it("SKYRAMP_TEST_BASE_URL (global) overrides workspace.yml baseUrl", async () => {
|
|
328
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
329
|
-
services: [
|
|
330
|
-
{
|
|
331
|
-
serviceName: "frontend",
|
|
332
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
333
|
-
},
|
|
334
|
-
],
|
|
335
|
-
});
|
|
336
|
-
process.env.SKYRAMP_TEST_BASE_URL = "https://preview.example.com";
|
|
337
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://preview.example.com");
|
|
338
|
-
});
|
|
339
|
-
it("per-service SKYRAMP_TEST_SERVICE_URL_<NAME> overrides for the picked frontend service", async () => {
|
|
340
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
341
|
-
services: [
|
|
342
|
-
{ serviceName: "backend", api: { baseUrl: "http://localhost:8000" } },
|
|
343
|
-
{
|
|
344
|
-
serviceName: "demoshop-frontend",
|
|
345
|
-
api: { baseUrl: "http://localhost:5173" },
|
|
346
|
-
},
|
|
347
|
-
],
|
|
348
|
-
});
|
|
349
|
-
process.env.SKYRAMP_TEST_SERVICE_URL_DEMOSHOP_FRONTEND =
|
|
350
|
-
"https://frontend-preview.example.com";
|
|
351
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://frontend-preview.example.com");
|
|
352
|
-
});
|
|
353
|
-
it("sanitizes service name: hyphens, dots, colons, slashes to underscores", async () => {
|
|
354
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
355
|
-
services: [
|
|
356
|
-
{
|
|
357
|
-
serviceName: "supabase-nextjs",
|
|
358
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
359
|
-
},
|
|
360
|
-
],
|
|
361
|
-
});
|
|
362
|
-
process.env.SKYRAMP_TEST_SERVICE_URL_SUPABASE_NEXTJS =
|
|
363
|
-
"https://supabase-preview.example.com";
|
|
364
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://supabase-preview.example.com");
|
|
365
|
-
});
|
|
366
|
-
it("sanitizes service name with dot: my.app -> MY_APP", async () => {
|
|
367
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
368
|
-
services: [
|
|
369
|
-
{ serviceName: "my.app", api: { baseUrl: "http://localhost:3000" } },
|
|
370
|
-
],
|
|
371
|
-
});
|
|
372
|
-
process.env.SKYRAMP_TEST_SERVICE_URL_MY_APP =
|
|
373
|
-
"https://myapp-preview.example.com";
|
|
374
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://myapp-preview.example.com");
|
|
375
|
-
});
|
|
376
|
-
it("sanitizes service name with colon: proto://app -> PROTO___APP", async () => {
|
|
377
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
378
|
-
services: [
|
|
379
|
-
{
|
|
380
|
-
serviceName: "proto://app",
|
|
381
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
});
|
|
385
|
-
process.env.SKYRAMP_TEST_SERVICE_URL_PROTO___APP =
|
|
386
|
-
"https://proto-preview.example.com";
|
|
387
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://proto-preview.example.com");
|
|
388
|
-
});
|
|
389
|
-
it("per-service takes precedence over global when both are set", async () => {
|
|
390
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
391
|
-
services: [
|
|
392
|
-
{
|
|
393
|
-
serviceName: "frontend",
|
|
394
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
});
|
|
398
|
-
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
399
|
-
process.env.SKYRAMP_TEST_SERVICE_URL_FRONTEND =
|
|
400
|
-
"https://per-service-preview.example.com";
|
|
401
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://per-service-preview.example.com");
|
|
402
|
-
});
|
|
403
|
-
it("empty/whitespace global env var is ignored, falls back to workspace.yml", async () => {
|
|
404
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
405
|
-
services: [
|
|
406
|
-
{
|
|
407
|
-
serviceName: "frontend",
|
|
408
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
409
|
-
},
|
|
410
|
-
],
|
|
411
|
-
});
|
|
412
|
-
process.env.SKYRAMP_TEST_BASE_URL = " ";
|
|
413
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:3000");
|
|
414
|
-
});
|
|
415
|
-
it("empty/whitespace per-service env var is ignored, falls back to global", async () => {
|
|
416
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
417
|
-
services: [
|
|
418
|
-
{
|
|
419
|
-
serviceName: "frontend",
|
|
420
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
421
|
-
},
|
|
422
|
-
],
|
|
423
|
-
});
|
|
424
|
-
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
425
|
-
process.env.SKYRAMP_TEST_SERVICE_URL_FRONTEND = " ";
|
|
426
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://global-preview.example.com");
|
|
427
|
-
});
|
|
428
|
-
it("global override returned even with no matching service", async () => {
|
|
429
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
430
|
-
services: [
|
|
431
|
-
{
|
|
432
|
-
serviceName: "backend",
|
|
433
|
-
language: "python",
|
|
434
|
-
api: { baseUrl: "http://localhost:8000" },
|
|
435
|
-
},
|
|
436
|
-
],
|
|
437
|
-
});
|
|
438
|
-
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
439
|
-
// pickFrontendBaseUrl will select the first service with baseUrl (H3), but the override takes precedence
|
|
440
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://global-preview.example.com");
|
|
441
|
-
});
|
|
442
|
-
it("existing no-override behavior still returns workspace.yml value", async () => {
|
|
443
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
444
|
-
services: [
|
|
445
|
-
{
|
|
446
|
-
serviceName: "frontend",
|
|
447
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
});
|
|
451
|
-
// No env vars set
|
|
452
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:3000");
|
|
453
|
-
});
|
|
454
|
-
it("does not throw when picked service has a non-string serviceName (unvalidated YAML)", async () => {
|
|
455
|
-
// readWorkspaceConfigRaw can fall back to an unvalidated YAML parse where
|
|
456
|
-
// serviceName is e.g. a number. The per-service guard must skip it rather
|
|
457
|
-
// than calling sanitizeServiceName on a non-string and throwing.
|
|
458
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
459
|
-
services: [
|
|
460
|
-
{
|
|
461
|
-
serviceName: 123,
|
|
462
|
-
language: "typescript",
|
|
463
|
-
framework: "next",
|
|
464
|
-
api: { baseUrl: "http://localhost:3000" },
|
|
465
|
-
},
|
|
466
|
-
],
|
|
467
|
-
});
|
|
468
|
-
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
469
|
-
// Falls through the per-service branch to the global override.
|
|
470
|
-
expect(await pickFrontendBaseUrl("/repo")).toBe("https://global-preview.example.com");
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
// ---------------------------------------------------------------------------
|
|
475
|
-
// findRootFallbackPage
|
|
476
|
-
// ---------------------------------------------------------------------------
|
|
477
|
-
describe("findRootFallbackPage", () => {
|
|
478
|
-
afterEach(() => {
|
|
479
|
-
readWorkspaceConfigRaw.mockReset();
|
|
480
|
-
});
|
|
481
|
-
it("returns null when no frontend baseUrl resolvable", async () => {
|
|
482
|
-
readWorkspaceConfigRaw.mockResolvedValue(null);
|
|
483
|
-
expect(await findRootFallbackPage("/repo")).toBeNull();
|
|
484
|
-
});
|
|
485
|
-
it("returns root-fallback page when frontend baseUrl resolves", async () => {
|
|
486
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
487
|
-
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
488
|
-
});
|
|
489
|
-
const page = await findRootFallbackPage("/repo");
|
|
490
|
-
expect(page).toEqual({
|
|
491
|
-
url: "http://localhost:3000",
|
|
492
|
-
sourcedFrom: [],
|
|
493
|
-
strategy: "root-fallback",
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
// ---------------------------------------------------------------------------
|
|
498
|
-
// enumerateCandidateUiPages
|
|
499
|
-
// ---------------------------------------------------------------------------
|
|
500
|
-
describe("enumerateCandidateUiPages", () => {
|
|
501
|
-
afterEach(() => {
|
|
502
|
-
readWorkspaceConfigRaw.mockReset();
|
|
503
|
-
});
|
|
504
|
-
it("returns empty for empty file list", async () => {
|
|
505
|
-
expect(await enumerateCandidateUiPages("/repo", [])).toEqual([]);
|
|
506
|
-
});
|
|
507
|
-
it("returns empty when workspace has no resolvable baseUrl", async () => {
|
|
508
|
-
readWorkspaceConfigRaw.mockResolvedValue(null);
|
|
509
|
-
expect(await enumerateCandidateUiPages("/repo", ["app/orders/page.tsx"])).toEqual([]);
|
|
510
|
-
});
|
|
511
|
-
it("uses strategy 1 (framework-route-grep) when route files match", async () => {
|
|
512
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
513
|
-
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
514
|
-
});
|
|
515
|
-
const repo = makeTmpRepo(["next.config.js"]);
|
|
516
|
-
const pages = await enumerateCandidateUiPages(repo, [
|
|
517
|
-
"app/orders/page.tsx",
|
|
518
|
-
"src/components/Button.tsx", // ignored — not a route
|
|
519
|
-
]);
|
|
520
|
-
expect(pages).toHaveLength(1);
|
|
521
|
-
expect(pages[0]).toMatchObject({
|
|
522
|
-
url: "http://localhost:3000/orders",
|
|
523
|
-
strategy: "framework-route-grep",
|
|
524
|
-
});
|
|
525
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
526
|
-
});
|
|
527
|
-
it("falls back to strategy 3 (root-fallback) when no route files match", async () => {
|
|
528
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
529
|
-
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
530
|
-
});
|
|
531
|
-
const pages = await enumerateCandidateUiPages("/repo", [
|
|
532
|
-
"src/components/Button.tsx", // not a route
|
|
533
|
-
"src/state/store.ts", // not a route
|
|
534
|
-
]);
|
|
535
|
-
expect(pages).toHaveLength(1);
|
|
536
|
-
expect(pages[0]).toMatchObject({
|
|
537
|
-
url: "http://localhost:3000",
|
|
538
|
-
strategy: "root-fallback",
|
|
539
|
-
});
|
|
540
|
-
});
|
|
541
|
-
it("emits multiple route pages when several match strategy 1", async () => {
|
|
542
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
543
|
-
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
544
|
-
});
|
|
545
|
-
// Need a real repoPath with framework config so detectsFilesystemRouting passes.
|
|
546
|
-
const repo = makeTmpRepo(["next.config.js"]);
|
|
547
|
-
const pages = await enumerateCandidateUiPages(repo, [
|
|
548
|
-
"app/orders/page.tsx",
|
|
549
|
-
"app/products/[id]/page.tsx",
|
|
550
|
-
]);
|
|
551
|
-
expect(pages).toHaveLength(2);
|
|
552
|
-
const urls = pages.map((p) => p.url).sort();
|
|
553
|
-
expect(urls).toEqual([
|
|
554
|
-
"http://localhost:3000/orders",
|
|
555
|
-
"http://localhost:3000/products/:id",
|
|
556
|
-
]);
|
|
557
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
558
|
-
});
|
|
559
|
-
it("uses strategy 2 (source-grounded-routes) when strategy 1 doesn't match but routes are declared in source", async () => {
|
|
560
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
561
|
-
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:5173" } }],
|
|
562
|
-
});
|
|
563
|
-
// Repo with no framework config (so strategy 1 won't fire) but with a
|
|
564
|
-
// React Router App.tsx declaring routes.
|
|
565
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-ladder-"));
|
|
566
|
-
fs.mkdirSync(path.join(repo, "src", "pages"), { recursive: true });
|
|
567
|
-
fs.writeFileSync(path.join(repo, "src", "App.tsx"), `
|
|
568
|
-
import { Routes, Route } from 'react-router-dom';
|
|
569
|
-
import Cart from './pages/Cart';
|
|
570
|
-
export default function App() { return <Routes><Route path="/cart" element={<Cart />} /></Routes>; }
|
|
571
|
-
`);
|
|
572
|
-
fs.writeFileSync(path.join(repo, "src", "pages", "Cart.tsx"), "export default function Cart() { return <div/>; }");
|
|
573
|
-
const pages = await enumerateCandidateUiPages(repo, [
|
|
574
|
-
"src/pages/Cart.tsx",
|
|
575
|
-
]);
|
|
576
|
-
expect(pages).toHaveLength(1);
|
|
577
|
-
expect(pages[0]).toMatchObject({
|
|
578
|
-
url: "http://localhost:5173/cart",
|
|
579
|
-
strategy: "source-grounded-routes",
|
|
580
|
-
});
|
|
581
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
582
|
-
});
|
|
583
|
-
it("falls back to root when repo lacks a framework config (no false positives)", async () => {
|
|
584
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
585
|
-
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
586
|
-
});
|
|
587
|
-
// Repo has files matching the Pages Router pattern but no next.config —
|
|
588
|
-
// this is the demoshop case (React with conventional layout, no Next.js).
|
|
589
|
-
const repo = makeTmpRepo([]);
|
|
590
|
-
const pages = await enumerateCandidateUiPages(repo, [
|
|
591
|
-
"frontend/src/pages/Cart.tsx",
|
|
592
|
-
]);
|
|
593
|
-
expect(pages).toHaveLength(1);
|
|
594
|
-
expect(pages[0]).toMatchObject({
|
|
595
|
-
url: "http://localhost:3000",
|
|
596
|
-
strategy: "root-fallback",
|
|
597
|
-
});
|
|
598
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
599
|
-
});
|
|
600
|
-
// Flutter end-to-end: pubspec.yaml declares the Flutter SDK, .dart files
|
|
601
|
-
// appear in the diff (passed in by callers after isFrontendFile filtering),
|
|
602
|
-
// workspace.yml has a baseUrl. Strategies 1, 2, and 2.5 don't match
|
|
603
|
-
// (no framework config; TS extractor doesn't see .dart; no GoRoute calls).
|
|
604
|
-
// Result: root-fallback with the workspace baseUrl.
|
|
605
|
-
it("returns root-fallback for a Flutter repo with no GoRoute declarations", async () => {
|
|
606
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
607
|
-
services: [
|
|
608
|
-
{
|
|
609
|
-
serviceName: "birdle-frontend",
|
|
610
|
-
framework: "playwright",
|
|
611
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
612
|
-
},
|
|
613
|
-
],
|
|
614
|
-
});
|
|
615
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-flutter-enum-"));
|
|
616
|
-
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: birdle\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
617
|
-
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
618
|
-
fs.writeFileSync(path.join(repo, "lib", "main.dart"), "void main() => runApp(MyApp());");
|
|
619
|
-
const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
|
|
620
|
-
expect(pages).toHaveLength(1);
|
|
621
|
-
expect(pages[0]).toMatchObject({
|
|
622
|
-
url: "http://localhost:8080",
|
|
623
|
-
strategy: "root-fallback",
|
|
624
|
-
});
|
|
625
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
626
|
-
});
|
|
627
|
-
// Strategy 2.5: Flutter repo with GoRouter declarations and no diff-match
|
|
628
|
-
// (frontendFiles names a screen file that's not imported by the router)
|
|
629
|
-
// surfaces every absolute route as a candidate URL via the all-routes
|
|
630
|
-
// fallback. Diff-match narrowing is exercised by the next test.
|
|
631
|
-
it("returns dart-go-router URLs for a Flutter repo with GoRoute declarations", async () => {
|
|
632
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
633
|
-
services: [
|
|
634
|
-
{
|
|
635
|
-
serviceName: "books-frontend",
|
|
636
|
-
framework: "playwright",
|
|
637
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
638
|
-
},
|
|
639
|
-
],
|
|
640
|
-
});
|
|
641
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-"));
|
|
642
|
-
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: books\ndependencies:\n flutter:\n sdk: flutter\n go_router: ^14.0.0\n");
|
|
643
|
-
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
644
|
-
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
645
|
-
final router = GoRouter(routes: [
|
|
646
|
-
GoRoute(path: '/signin', builder: (c, s) => SignIn()),
|
|
647
|
-
GoRoute(path: '/authors', builder: (c, s) => Authors()),
|
|
648
|
-
GoRoute(path: '/settings', builder: (c, s) => Settings()),
|
|
649
|
-
]);
|
|
650
|
-
`);
|
|
651
|
-
const pages = await enumerateCandidateUiPages(repo, ["lib/screens/authors.dart"]);
|
|
652
|
-
const urls = pages.map((p) => p.url).sort();
|
|
653
|
-
expect(urls).toEqual([
|
|
654
|
-
"http://localhost:8080/authors",
|
|
655
|
-
"http://localhost:8080/settings",
|
|
656
|
-
"http://localhost:8080/signin",
|
|
657
|
-
]);
|
|
658
|
-
expect(pages.every((p) => p.strategy === "dart-go-router")).toBe(true);
|
|
659
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
660
|
-
});
|
|
661
|
-
// Diff-matching: Strategy 2.5 should narrow candidate URLs to only those
|
|
662
|
-
// whose screen file is in frontendFiles. The fixture has 3 routes; only
|
|
663
|
-
// /authors's screen file (lib/src/screens/authors.dart) is in the diff.
|
|
664
|
-
it("narrows GoRouter candidates to routes whose screen file is in the diff", async () => {
|
|
665
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
666
|
-
services: [
|
|
667
|
-
{
|
|
668
|
-
serviceName: "books-frontend",
|
|
669
|
-
framework: "playwright",
|
|
670
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
671
|
-
},
|
|
672
|
-
],
|
|
673
|
-
});
|
|
674
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-diff-"));
|
|
675
|
-
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: books\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
676
|
-
fs.mkdirSync(path.join(repo, "lib", "src", "screens"), { recursive: true });
|
|
677
|
-
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
678
|
-
import 'src/screens/authors.dart';
|
|
679
|
-
import 'src/screens/settings.dart';
|
|
680
|
-
import 'src/screens/sign_in.dart';
|
|
681
|
-
|
|
682
|
-
final router = GoRouter(routes: [
|
|
683
|
-
GoRoute(path: '/sign_in', builder: (c, s) => SignInScreen()),
|
|
684
|
-
GoRoute(path: '/authors', builder: (c, s) => AuthorsScreen()),
|
|
685
|
-
GoRoute(path: '/settings', builder: (c, s) => SettingsScreen()),
|
|
686
|
-
]);
|
|
687
|
-
`);
|
|
688
|
-
fs.writeFileSync(path.join(repo, "lib", "src", "screens", "authors.dart"), "// authors");
|
|
689
|
-
fs.writeFileSync(path.join(repo, "lib", "src", "screens", "settings.dart"), "// settings");
|
|
690
|
-
fs.writeFileSync(path.join(repo, "lib", "src", "screens", "sign_in.dart"), "// signin");
|
|
691
|
-
const pages = await enumerateCandidateUiPages(repo, ["lib/src/screens/authors.dart"]);
|
|
692
|
-
const urls = pages.map((p) => p.url).sort();
|
|
693
|
-
// Only /authors should surface — its screen file is the only one in the diff.
|
|
694
|
-
expect(urls).toEqual(["http://localhost:8080/authors"]);
|
|
695
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
696
|
-
});
|
|
697
|
-
// Redirect-only routes (`redirect:` with no builder) must not surface — they
|
|
698
|
-
// have no UI to navigate to. Same fixture shape but the only diff-matching
|
|
699
|
-
// route is redirect-only, so we should fall back to the non-redirect-only
|
|
700
|
-
// routes.
|
|
701
|
-
it("filters out redirect-only GoRoutes", async () => {
|
|
702
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
703
|
-
services: [
|
|
704
|
-
{
|
|
705
|
-
serviceName: "frontend",
|
|
706
|
-
framework: "playwright",
|
|
707
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
708
|
-
},
|
|
709
|
-
],
|
|
710
|
-
});
|
|
711
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-redirect-"));
|
|
712
|
-
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: app\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
713
|
-
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
714
|
-
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
715
|
-
final router = GoRouter(routes: [
|
|
716
|
-
GoRoute(path: '/', redirect: (_, _) => '/home'),
|
|
717
|
-
GoRoute(path: '/home', builder: (c, s) => Home()),
|
|
718
|
-
]);
|
|
719
|
-
`);
|
|
720
|
-
const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
|
|
721
|
-
const urls = pages.map((p) => p.url).sort();
|
|
722
|
-
// / is redirect-only — must not appear. Only /home is renderable.
|
|
723
|
-
expect(urls).toEqual(["http://localhost:8080/home"]);
|
|
724
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
725
|
-
});
|
|
726
|
-
// Fallback path: when no route's screen file matches the diff (e.g. router
|
|
727
|
-
// file edited but our import-resolution heuristic missed the screen), surface
|
|
728
|
-
// ALL renderable routes rather than collapsing to empty.
|
|
729
|
-
it("falls back to all renderable routes when no screen file matches the diff", async () => {
|
|
730
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
731
|
-
services: [
|
|
732
|
-
{
|
|
733
|
-
serviceName: "frontend",
|
|
734
|
-
framework: "playwright",
|
|
735
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
736
|
-
},
|
|
737
|
-
],
|
|
738
|
-
});
|
|
739
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-fallback-"));
|
|
740
|
-
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: app\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
741
|
-
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
742
|
-
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
743
|
-
final router = GoRouter(routes: [
|
|
744
|
-
GoRoute(path: '/foo', builder: (c, s) => FooScreen()),
|
|
745
|
-
GoRoute(path: '/bar', builder: (c, s) => BarScreen()),
|
|
746
|
-
]);
|
|
747
|
-
`);
|
|
748
|
-
// Diff touches only main.dart (the router itself, not any screen). The
|
|
749
|
-
// import-resolution heuristic finds zero matches against frontendFiles, so
|
|
750
|
-
// we surface all renderable routes.
|
|
751
|
-
const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
|
|
752
|
-
const urls = pages.map((p) => p.url).sort();
|
|
753
|
-
expect(urls).toEqual([
|
|
754
|
-
"http://localhost:8080/bar",
|
|
755
|
-
"http://localhost:8080/foo",
|
|
756
|
-
]);
|
|
757
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
758
|
-
});
|
|
759
|
-
// Negative gate: pure-Dart server with a `GoRoute(`-shaped string in source
|
|
760
|
-
// (e.g. shelf_router uses similar names in tests) must NOT trigger Strategy
|
|
761
|
-
// 2.5 — pubspec.yaml lacks `sdk: flutter`, hasFlutterSdkDep returns false.
|
|
762
|
-
it("does not invoke Dart extractor when pubspec.yaml lacks sdk: flutter", async () => {
|
|
763
|
-
readWorkspaceConfigRaw.mockResolvedValue({
|
|
764
|
-
services: [
|
|
765
|
-
{
|
|
766
|
-
serviceName: "server-frontend",
|
|
767
|
-
framework: "playwright",
|
|
768
|
-
api: { baseUrl: "http://localhost:8080" },
|
|
769
|
-
},
|
|
770
|
-
],
|
|
771
|
-
});
|
|
772
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-dart-server-"));
|
|
773
|
-
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: server\ndependencies:\n shelf: ^1.0.0\n");
|
|
774
|
-
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
775
|
-
fs.writeFileSync(path.join(repo, "lib", "routes.dart"), `GoRoute(path: '/api/v1', handler: (req) => Response.ok(''))`);
|
|
776
|
-
const pages = await enumerateCandidateUiPages(repo, ["lib/routes.dart"]);
|
|
777
|
-
expect(pages.map((p) => p.strategy)).toEqual(["root-fallback"]);
|
|
778
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
// ---------------------------------------------------------------------------
|
|
782
|
-
// detectsFilesystemRouting
|
|
783
|
-
// ---------------------------------------------------------------------------
|
|
784
|
-
describe("detectsFilesystemRouting", () => {
|
|
785
|
-
let repo;
|
|
786
|
-
afterEach(() => {
|
|
787
|
-
if (repo) {
|
|
788
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
789
|
-
repo = "";
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
it("returns false when no framework config exists", () => {
|
|
793
|
-
repo = makeTmpRepo([]);
|
|
794
|
-
expect(detectsFilesystemRouting(repo)).toBe(false);
|
|
795
|
-
});
|
|
796
|
-
it("detects Next.js via next.config.js at repo root", () => {
|
|
797
|
-
repo = makeTmpRepo(["next.config.js"]);
|
|
798
|
-
expect(detectsFilesystemRouting(repo)).toBe(true);
|
|
799
|
-
});
|
|
800
|
-
it("detects Next.js via next.config.ts", () => {
|
|
801
|
-
repo = makeTmpRepo(["next.config.ts"]);
|
|
802
|
-
expect(detectsFilesystemRouting(repo)).toBe(true);
|
|
803
|
-
});
|
|
804
|
-
it("detects Nuxt via nuxt.config.ts", () => {
|
|
805
|
-
repo = makeTmpRepo(["nuxt.config.ts"]);
|
|
806
|
-
expect(detectsFilesystemRouting(repo)).toBe(true);
|
|
807
|
-
});
|
|
808
|
-
it("detects SvelteKit via svelte.config.js", () => {
|
|
809
|
-
repo = makeTmpRepo(["svelte.config.js"]);
|
|
810
|
-
expect(detectsFilesystemRouting(repo)).toBe(true);
|
|
811
|
-
});
|
|
812
|
-
it("detects framework config one level deep (frontend/)", () => {
|
|
813
|
-
repo = makeTmpRepo(["frontend/next.config.js"]);
|
|
814
|
-
expect(detectsFilesystemRouting(repo)).toBe(true);
|
|
815
|
-
});
|
|
816
|
-
it("detects framework config in apps/web/", () => {
|
|
817
|
-
repo = makeTmpRepo(["apps/web/next.config.mjs"]);
|
|
818
|
-
expect(detectsFilesystemRouting(repo)).toBe(true);
|
|
819
|
-
});
|
|
820
|
-
it("returns false when only a pages/ directory exists (no config)", () => {
|
|
821
|
-
repo = makeTmpRepo(["pages/Cart.tsx"]);
|
|
822
|
-
expect(detectsFilesystemRouting(repo)).toBe(false);
|
|
823
|
-
});
|
|
824
|
-
});
|