@lowdefy/server-dev 6.1.0 → 7.0.0
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/client/App.jsx +52 -24
- package/client/Inspector.jsx +73 -30
- package/client/JourneyObserver.jsx +47 -0
- package/client/Page.jsx +54 -22
- package/client/Recorder.jsx +73 -0
- package/client/Routing.jsx +37 -13
- package/client/clientBuildImports.test.mjs +2 -0
- package/client/devStreamConnections.test.mjs +40 -0
- package/client/feedback/FeedbackMount.jsx +2 -2
- package/client/feedback/FeedbackOverlay.jsx +7 -4
- package/client/feedback/feedbackStyles.js +3 -0
- package/client/journeyObserver/startJourneyObserver.js +101 -0
- package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
- package/client/openInEditor/OpenInEditorListener.jsx +2 -0
- package/client/recorder/buildEngineRecord.js +37 -0
- package/client/recorder/buildInteractionRecord.js +64 -0
- package/client/recorder/buildTraceEvent.js +57 -0
- package/client/recorder/buildTraceEvent.test.mjs +81 -0
- package/client/recorder/chunkRecords.js +69 -0
- package/client/recorder/chunkRecords.test.mjs +59 -0
- package/client/recorder/collectEventActions.js +30 -0
- package/client/recorder/collectEventEndpoints.js +40 -0
- package/client/recorder/collectEventRequests.js +39 -0
- package/client/recorder/createPairingBuffer.js +187 -0
- package/client/recorder/createPairingBuffer.test.mjs +405 -0
- package/client/recorder/createPasswordRedactor.js +84 -0
- package/client/recorder/createRecordBatcher.js +54 -0
- package/client/recorder/createRecordBatcher.test.mjs +76 -0
- package/client/recorder/createRecorder.js +216 -0
- package/client/recorder/createRecorder.test.mjs +318 -0
- package/client/recorder/createRecordingSession.js +61 -0
- package/client/recorder/createRecordingSession.test.mjs +72 -0
- package/client/recorder/diffStateWrites.js +77 -0
- package/client/recorder/diffStateWrites.test.mjs +66 -0
- package/client/recorder/isEventCall.js +30 -0
- package/client/recorder/jsonType.js +39 -0
- package/client/recorder/keyChord.js +47 -0
- package/client/recorder/keyChord.test.mjs +50 -0
- package/client/recorder/sendRecords.js +40 -0
- package/client/recorder/toJsonSafe.js +29 -0
- package/lib/client/BuildingPage.jsx +3 -1
- package/lib/client/ErrorBar.jsx +17 -13
- package/lib/client/RedirectingPage.jsx +56 -0
- package/lib/client/auth/Auth.jsx +11 -6
- package/lib/client/auth/AuthConfigured.jsx +222 -36
- package/lib/client/auth/AuthNotConfigured.js +45 -4
- package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
- package/lib/client/auth/authTemplateSync.test.mjs +40 -0
- package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
- package/lib/client/auth/createFetchCheckingBuild.js +45 -0
- package/lib/client/bundleBuildId.js +25 -0
- package/lib/client/collectVisibleBlockIds.js +51 -0
- package/lib/client/getCspNonce.js +25 -0
- package/lib/client/getPagePath.js +36 -0
- package/lib/client/getPagePath.test.mjs +51 -0
- package/lib/client/utils/formatErrorsForCopy.js +30 -0
- package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
- package/lib/client/utils/getErrorBarColor.js +25 -0
- package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
- package/lib/client/utils/request.js +8 -1
- package/lib/client/utils/usePageConfig.js +89 -25
- package/lib/client/utils/usePageConfig.test.mjs +198 -13
- package/lib/configTree/buildConfigTree.mjs +58 -0
- package/lib/configTree/buildConfigTree.test.mjs +77 -0
- package/lib/configTree/collectConfigText.mjs +101 -0
- package/lib/configTree/collectConfigText.test.mjs +116 -0
- package/lib/configTree/prepareScratchServer.mjs +46 -0
- package/lib/configTree/runConfigTreeBuild.mjs +88 -0
- package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
- package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
- package/lib/docs/JourneyStepError.js +30 -0
- package/lib/docs/acquireBrowserSlot.js +30 -0
- package/lib/docs/addNestedEndpoints.js +54 -0
- package/lib/docs/addNestedEndpoints.test.mjs +185 -0
- package/lib/docs/addWriteFlags.js +77 -0
- package/lib/docs/buildEditedPages.js +50 -0
- package/lib/docs/buildWaitHeader.js +21 -0
- package/lib/docs/captureAnnotatedScreenshot.js +23 -12
- package/lib/docs/checkAgentFiles.js +148 -0
- package/lib/docs/checkAgentFiles.test.mjs +225 -0
- package/lib/docs/checkStepReferences.js +239 -0
- package/lib/docs/checkStepReferences.test.mjs +173 -0
- package/lib/docs/checkpointStore.js +3 -0
- package/lib/docs/clientErrorStore.js +18 -1
- package/lib/docs/clientErrorStore.test.mjs +46 -0
- package/lib/docs/collectExercised.js +52 -0
- package/lib/docs/createBrowserLifecycle.js +102 -0
- package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
- package/lib/docs/createDocsMcpServer.js +345 -388
- package/lib/docs/createDocsMcpServer.test.mjs +644 -4
- package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
- package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
- package/lib/docs/createJourneyActors.js +213 -0
- package/lib/docs/createJourneyActors.test.mjs +79 -0
- package/lib/docs/createJourneyObservations.js +80 -0
- package/lib/docs/createLeftOriginError.js +26 -0
- package/lib/docs/createNetworkCounter.js +101 -0
- package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/deserializeFixture.js +34 -0
- package/lib/docs/dataSets/drainSessionWork.js +37 -0
- package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
- package/lib/docs/dataSets/duplicateIndexName.js +24 -0
- package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
- package/lib/docs/dataSets/getDataStore.js +106 -0
- package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
- package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
- package/lib/docs/dataSets/insertFixture.js +57 -0
- package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
- package/lib/docs/dataSets/isDataStoreGone.js +29 -0
- package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
- package/lib/docs/dataSets/openDataSession.js +119 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
- package/lib/docs/dataSets/pickDataStorePort.js +58 -0
- package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
- package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
- package/lib/docs/dataSets/readDataSession.js +41 -0
- package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
- package/lib/docs/dataSets/readDataSet.js +68 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
- package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
- package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
- package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
- package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
- package/lib/docs/devEventBus.js +136 -0
- package/lib/docs/devEventBus.test.mjs +269 -0
- package/lib/docs/devToolDefinitions.js +529 -0
- package/lib/docs/docSources.js +21 -0
- package/lib/docs/docs.test.mjs +171 -15
- package/lib/docs/docsIndex.test.mjs +377 -0
- package/lib/docs/evalOperator.js +5 -3
- package/lib/docs/evalOperatorHeadless.js +84 -12
- package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
- package/lib/docs/evalOperatorInTab.js +2 -1
- package/lib/docs/findConfig.js +95 -49
- package/lib/docs/fitResponse.js +81 -0
- package/lib/docs/fitResponse.test.mjs +128 -0
- package/lib/docs/formatFeedback.js +26 -5
- package/lib/docs/formatFeedback.test.mjs +48 -0
- package/lib/docs/getAppMap.js +7 -1
- package/lib/docs/getBrowser.js +150 -80
- package/lib/docs/getBrowser.test.mjs +407 -16
- package/lib/docs/getBuildId.js +61 -0
- package/lib/docs/getBuildId.test.mjs +84 -0
- package/lib/docs/getBuildStatus.js +54 -6
- package/lib/docs/getBuildStatus.test.mjs +133 -0
- package/lib/docs/getBuildStatusAfterEdits.js +35 -0
- package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
- package/lib/docs/getCoreDoc.js +9 -1
- package/lib/docs/getDoc.js +100 -0
- package/lib/docs/getDocsIndex.js +152 -0
- package/lib/docs/getHazards.js +115 -0
- package/lib/docs/getHazards.test.mjs +175 -0
- package/lib/docs/getMailOutboxDirectory.js +26 -0
- package/lib/docs/getOverview.js +79 -13
- package/lib/docs/getOverviewBasePath.test.mjs +37 -0
- package/lib/docs/getPageBuildStatus.js +46 -0
- package/lib/docs/getPluginDoc.js +21 -23
- package/lib/docs/getSchema.js +33 -1
- package/lib/docs/getStaleStatus.js +36 -0
- package/lib/docs/getStaleStatus.test.mjs +74 -0
- package/lib/docs/guardJourneyOrigin.js +39 -0
- package/lib/docs/inspectState.js +5 -3
- package/lib/docs/inspectStateFromTab.js +2 -2
- package/lib/docs/inspectStateHeadless.js +89 -27
- package/lib/docs/inspectStateHeadless.test.mjs +20 -1
- package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
- package/lib/docs/installHeadlessShell.js +123 -0
- package/lib/docs/installHeadlessShell.test.mjs +70 -0
- package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
- package/lib/docs/isHeadlessShellIncomplete.js +34 -0
- package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
- package/lib/docs/isPageReady.js +77 -0
- package/lib/docs/isPageReady.test.mjs +235 -0
- package/lib/docs/isRunErrorEntry.js +25 -0
- package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
- package/lib/docs/journeyRunRecording.js +35 -0
- package/lib/docs/journeyRunRecording.test.mjs +39 -0
- package/lib/docs/keepEventStreamAlive.js +68 -0
- package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
- package/lib/docs/launchBrowser.js +83 -0
- package/lib/docs/launchBrowser.test.mjs +175 -0
- package/lib/docs/listModuleDocEntries.js +69 -0
- package/lib/docs/listPackageDocFiles.js +67 -0
- package/lib/docs/listPluginDocEntries.js +111 -0
- package/lib/docs/listPlugins.js +9 -58
- package/lib/docs/listTypes.js +14 -6
- package/lib/docs/loadFrameworkHazards.js +40 -0
- package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
- package/lib/docs/loadState.js +103 -35
- package/lib/docs/makeModuleManifestDoc.js +174 -0
- package/lib/docs/matchExercisedPages.js +35 -0
- package/lib/docs/matchJourneyRoute.js +69 -0
- package/lib/docs/matchJourneyRoute.test.mjs +178 -0
- package/lib/docs/mergeNetworkSnapshots.js +49 -0
- package/lib/docs/nextJourneySequence.js +30 -0
- package/lib/docs/noBrowserError.js +22 -0
- package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
- package/lib/docs/observe/claimExpectedErrors.js +67 -0
- package/lib/docs/observe/createJourneyAppErrors.js +236 -0
- package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
- package/lib/docs/observe/evaluateInvariants.js +183 -0
- package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
- package/lib/docs/observe/findingKey.js +29 -0
- package/lib/docs/observe/hasNoEffect.js +31 -0
- package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
- package/lib/docs/observe/installStepObserver.js +72 -0
- package/lib/docs/observe/isAppApiUrl.js +33 -0
- package/lib/docs/observe/parseAppApiUrl.js +37 -0
- package/lib/docs/observe/readStepObserver.js +42 -0
- package/lib/docs/observe/relativeSource.js +29 -0
- package/lib/docs/observe/resolveConfigKeySource.js +33 -0
- package/lib/docs/observe/takeJourneyEvents.js +36 -0
- package/lib/docs/observe/toAppErrorFailure.js +61 -0
- package/lib/docs/observe/usesSearchStage.js +45 -0
- package/lib/docs/observe/waitForClientErrorReports.js +31 -0
- package/lib/docs/observe/watchJourneyContext.js +75 -0
- package/lib/docs/observe/withAppErrors.js +31 -0
- package/lib/docs/observe/withAppErrors.test.mjs +101 -0
- package/lib/docs/openJourney.js +94 -0
- package/lib/docs/openJourneyEmail.js +41 -0
- package/lib/docs/openJourneyEmail.test.mjs +187 -0
- package/lib/docs/pageBlocks.js +47 -0
- package/lib/docs/pageBuildStatus.test.mjs +103 -0
- package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
- package/lib/docs/rails/authEngineActions.js +53 -0
- package/lib/docs/rails/authEngineActions.test.mjs +69 -0
- package/lib/docs/rails/authEngineMethods.js +52 -0
- package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
- package/lib/docs/rails/checkAuthActionsRan.js +66 -0
- package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
- package/lib/docs/rails/checkClickRails.js +56 -0
- package/lib/docs/rails/createDataSetRails.js +82 -0
- package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
- package/lib/docs/rails/findExternalBlocks.js +147 -0
- package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
- package/lib/docs/rails/installAuthActionRecorder.js +56 -0
- package/lib/docs/rails/readAuthActionRecorder.js +32 -0
- package/lib/docs/rails/readBuiltArtifact.js +30 -0
- package/lib/docs/readConnectionPlugins.js +26 -0
- package/lib/docs/readDevAuthMode.js +32 -0
- package/lib/docs/readDocEntry.js +40 -0
- package/lib/docs/readDocTitle.js +26 -0
- package/lib/docs/readJourneyEmailMatch.js +57 -0
- package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
- package/lib/docs/readJourneySession.js +56 -0
- package/lib/docs/readMailOutbox.js +37 -0
- package/lib/docs/readPagePath.js +27 -0
- package/lib/docs/readPluginPackages.js +99 -0
- package/lib/docs/readProxyBuildWait.js +38 -0
- package/lib/docs/readRoutineSteps.js +45 -0
- package/lib/docs/registerShownTextEngine.js +118 -0
- package/lib/docs/requestRestart.js +30 -0
- package/lib/docs/requestRestart.test.mjs +49 -0
- package/lib/docs/resolvePageInstance.js +46 -0
- package/lib/docs/resolvePageInstance.test.mjs +81 -0
- package/lib/docs/resolveSource.js +11 -7
- package/lib/docs/resolveSource.test.mjs +1 -1
- package/lib/docs/resolveToolCaller.js +62 -0
- package/lib/docs/reviewPage.js +51 -0
- package/lib/docs/reviewPageBuilds.js +56 -0
- package/lib/docs/reviewPageBuilds.test.mjs +207 -0
- package/lib/docs/runCheck.js +75 -0
- package/lib/docs/runCheck.test.mjs +91 -0
- package/lib/docs/runCheckChild.js +49 -0
- package/lib/docs/runEndpoint.js +185 -0
- package/lib/docs/runEndpoint.test.mjs +509 -0
- package/lib/docs/runErrorBuffers.js +99 -0
- package/lib/docs/runErrorBuffers.test.mjs +95 -0
- package/lib/docs/runJourney.js +347 -0
- package/lib/docs/runJourney.test.mjs +2383 -0
- package/lib/docs/runJourneyAsUsers.js +44 -0
- package/lib/docs/runJourneyDataSet.test.mjs +274 -0
- package/lib/docs/runJourneySteps.js +1320 -0
- package/lib/docs/runRequest.js +100 -59
- package/lib/docs/runRequest.test.mjs +190 -0
- package/lib/docs/saveAnnotatedScreenshot.js +1 -1
- package/lib/docs/scaffoldPage.js +3 -1
- package/lib/docs/screenshotPage.js +163 -11
- package/lib/docs/screenshotPage.test.mjs +263 -1
- package/lib/docs/screenshotPageClip.test.mjs +4 -0
- package/lib/docs/searchDocs.js +82 -30
- package/lib/docs/searchDocs.test.mjs +100 -0
- package/lib/docs/searchIcons.js +90 -0
- package/lib/docs/searchIcons.test.mjs +127 -0
- package/lib/docs/selectExercised.js +39 -0
- package/lib/docs/selectFinalState.js +66 -0
- package/lib/docs/selectFinalState.test.mjs +51 -0
- package/lib/docs/serverErrorStore.js +54 -0
- package/lib/docs/serverErrorStore.test.mjs +50 -0
- package/lib/docs/setupTestFixtures.mjs +133 -39
- package/lib/docs/shownTextSelector.js +28 -0
- package/lib/docs/snapshotState.js +13 -5
- package/lib/docs/splitSearchTerms.js +62 -0
- package/lib/docs/splitSearchTerms.test.mjs +50 -0
- package/lib/docs/stateCheckpoints.test.mjs +77 -0
- package/lib/docs/tabAvailable.js +6 -5
- package/lib/docs/tabChannel.js +127 -21
- package/lib/docs/tabChannel.test.mjs +187 -29
- package/lib/docs/unsettledPageNote.js +24 -0
- package/lib/docs/validateJourneyMail.js +36 -0
- package/lib/docs/validateJourneyTimeout.js +38 -0
- package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
- package/lib/docs/validateRecordingParam.js +42 -0
- package/lib/docs/validateStateSelection.js +33 -0
- package/lib/docs/validateStateSelection.test.mjs +33 -0
- package/lib/docs/validateViewport.js +51 -0
- package/lib/docs/validateViewport.test.mjs +54 -0
- package/lib/docs/waitForBuild.js +50 -0
- package/lib/docs/waitForBuild.test.mjs +75 -0
- package/lib/docs/waitForJourneyEmail.js +77 -0
- package/lib/docs/withBrowserSlot.js +45 -0
- package/lib/docs/withBrowserSlot.test.mjs +75 -0
- package/lib/docs/withDataSession.js +53 -0
- package/lib/server/applyDataSetRedirect.js +83 -0
- package/lib/server/applyDataSetRedirect.test.mjs +340 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
- package/lib/server/auth/getHeadlessUser.js +9 -8
- package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
- package/lib/server/auth/getMockUser.js +18 -14
- package/lib/server/auth/{session.js → getStrategies.js} +17 -16
- package/lib/server/auth/headlessUser.js +3 -2
- package/lib/server/auth/journeyActor.js +35 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
- package/lib/server/checkPageRecord.js +44 -0
- package/lib/server/contextMapBudget.js +24 -0
- package/lib/server/contextMapBudget.test.mjs +98 -0
- package/lib/server/continueJitKeys.js +28 -0
- package/lib/server/continueJitKeys.test.mjs +43 -0
- package/lib/server/createDataSessionEndedError.js +33 -0
- package/lib/server/createDataSetRefusal.js +26 -0
- package/lib/server/createLowdefyContext.js +168 -41
- package/lib/server/createLowdefyContext.test.mjs +328 -8
- package/lib/server/getClientAddress.js +51 -0
- package/lib/server/getClientAddress.test.mjs +56 -0
- package/lib/server/getPageJitEnrichment.test.mjs +29 -11
- package/lib/server/hashConfigContent.js +26 -0
- package/lib/server/importAppCode.test.mjs +57 -0
- package/lib/server/isRoutineStep.js +27 -0
- package/lib/server/jitPageBuilder.js +309 -126
- package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
- package/lib/server/jitPageBuilder.test.mjs +218 -0
- package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
- package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
- package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
- package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
- package/lib/server/journeyCookies.js +22 -0
- package/lib/server/journeyCookies.test.mjs +145 -0
- package/lib/server/loadDynamicJsMap.js +47 -0
- package/lib/server/log/createHandleError.js +38 -2
- package/lib/server/log/createHandleError.test.mjs +125 -2
- package/lib/server/log/createLogger.js +3 -0
- package/lib/server/log/createLogger.test.mjs +15 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
- package/lib/server/mutants/applyMutant.js +39 -0
- package/lib/server/mutants/artifactPatterns.js +25 -0
- package/lib/server/mutants/buildAnchor.js +45 -0
- package/lib/server/mutants/collectSharedIds.js +41 -0
- package/lib/server/mutants/configPathSegments.js +38 -0
- package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
- package/lib/server/mutants/describeEvent.js +22 -0
- package/lib/server/mutants/enumerateArtifact.js +56 -0
- package/lib/server/mutants/findNodeByKey.js +45 -0
- package/lib/server/mutants/findNodeByPointer.js +49 -0
- package/lib/server/mutants/getArtifactKind.js +32 -0
- package/lib/server/mutants/groupMutantCopies.js +56 -0
- package/lib/server/mutants/hashArtifact.js +25 -0
- package/lib/server/mutants/hashMutantTarget.js +34 -0
- package/lib/server/mutants/listMutants.js +122 -0
- package/lib/server/mutants/listMutants.test.mjs +502 -0
- package/lib/server/mutants/mutantId.js +30 -0
- package/lib/server/mutants/mutantRuns.js +57 -0
- package/lib/server/mutants/mutantRuns.test.mjs +136 -0
- package/lib/server/mutants/operators/dropAction.js +47 -0
- package/lib/server/mutants/operators/dropBlock.js +44 -0
- package/lib/server/mutants/operators/dropPayload.js +52 -0
- package/lib/server/mutants/operators/dropStep.js +44 -0
- package/lib/server/mutants/operators/flipVisible.js +53 -0
- package/lib/server/mutants/operators/index.js +37 -0
- package/lib/server/mutants/operators/retargetLink.js +72 -0
- package/lib/server/mutants/operators/skipValidate.js +77 -0
- package/lib/server/mutants/operators/swapIf.js +112 -0
- package/lib/server/mutants/operators.test.mjs +396 -0
- package/lib/server/mutants/positionFreePath.js +38 -0
- package/lib/server/mutants/removeFromParent.js +28 -0
- package/lib/server/mutants/test/fixtures.mjs +173 -0
- package/lib/server/mutants/test/keyArtifact.mjs +63 -0
- package/lib/server/mutants/validateMutantParam.js +69 -0
- package/lib/server/mutants/validateMutantsBody.js +88 -0
- package/lib/server/mutants/walkArtifact.js +179 -0
- package/lib/server/pageBuildRecords.js +131 -0
- package/lib/server/pageBuildRecords.test.mjs +258 -0
- package/lib/server/pageCache.mjs +22 -7
- package/lib/server/pageCache.test.mjs +22 -7
- package/lib/server/pageRebuilds.test.mjs +543 -0
- package/lib/server/pruneJitMaps.js +36 -0
- package/lib/server/pruneJitMaps.test.mjs +55 -0
- package/lib/server/readInstanceRecord.js +33 -0
- package/lib/server/readInstanceRecord.test.mjs +99 -0
- package/lib/server/readMergedMaps.js +86 -0
- package/lib/server/readMergedMaps.test.mjs +79 -0
- package/lib/server/recording/appendRecords.js +33 -0
- package/lib/server/recording/errorRecordingStamp.js +34 -0
- package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
- package/lib/server/recording/isRecordingEnabled.js +31 -0
- package/lib/server/recording/readRecordingCookie.js +71 -0
- package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
- package/lib/server/recording/recordingCookiePayload.js +33 -0
- package/lib/server/recording/servedBuilds.js +46 -0
- package/lib/server/recording/servedBuilds.test.mjs +42 -0
- package/lib/server/sinkExternalConnections.js +95 -0
- package/lib/server/sinkExternalConnections.test.mjs +135 -0
- package/lib/server/skipStaleMapWrites.js +62 -0
- package/lib/server/skipStaleMapWrites.test.mjs +113 -0
- package/lib/server/startIdleGc.js +67 -0
- package/lib/server/startIdleGc.test.mjs +138 -0
- package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
- package/lib/server/trackSessionWork.js +30 -0
- package/lib/vite/createDevServerExclude.js +40 -0
- package/lib/vite/createDevServerExclude.test.mjs +79 -0
- package/manager/getContext.mjs +24 -2
- package/manager/processes/initialBuild.mjs +14 -0
- package/manager/processes/initialBuild.test.mjs +101 -0
- package/manager/processes/lowdefyBuild.mjs +30 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
- package/manager/processes/lowdefyBuild.test.mjs +136 -1
- package/manager/processes/optimizeDependencies.mjs +59 -0
- package/manager/processes/restartServer.mjs +34 -6
- package/manager/processes/restartServer.test.mjs +162 -0
- package/manager/processes/shutdownServer.mjs +11 -0
- package/manager/processes/shutdownServer.test.mjs +45 -0
- package/manager/processes/startFirstServer.mjs +46 -0
- package/manager/processes/startFirstServer.test.mjs +326 -0
- package/manager/processes/startMailSink.mjs +135 -0
- package/manager/processes/startMailSink.test.mjs +146 -0
- package/manager/processes/startProxy.mjs +129 -12
- package/manager/processes/startProxy.test.mjs +366 -0
- package/manager/processes/startServer.mjs +47 -6
- package/manager/processes/startServer.test.mjs +155 -0
- package/manager/processes/startWatchers.mjs +6 -4
- package/manager/processes/syncServer.mjs +69 -0
- package/manager/processes/syncServer.test.mjs +141 -0
- package/manager/processes/waitForDevServer.mjs +48 -0
- package/manager/processes/waitForDevServer.test.mjs +112 -0
- package/manager/run.mjs +128 -43
- package/manager/utils/BatchChanges.mjs +21 -5
- package/manager/utils/BatchChanges.test.mjs +77 -0
- package/manager/utils/acquireDevInstance.mjs +99 -0
- package/manager/utils/acquireDevInstance.test.mjs +160 -0
- package/manager/utils/createBuildActivity.mjs +67 -0
- package/manager/utils/createBuildActivity.test.mjs +58 -0
- package/manager/utils/createChangeSignal.mjs +39 -0
- package/manager/utils/createChangeSignal.test.mjs +62 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
- package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
- package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
- package/manager/utils/createDotPathIgnore.mjs +44 -0
- package/manager/utils/createDotPathIgnore.test.mjs +65 -0
- package/manager/utils/createIdleGc.mjs +51 -0
- package/manager/utils/createIdleGc.test.mjs +77 -0
- package/manager/utils/createRequestActivity.mjs +100 -0
- package/manager/utils/createRequestActivity.test.mjs +121 -0
- package/manager/utils/createServerArtifactTracker.mjs +83 -0
- package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
- package/manager/utils/createServerEnv.mjs +39 -0
- package/manager/utils/filesHaveSameBytes.mjs +41 -0
- package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
- package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
- package/manager/utils/importFresh.mjs +46 -0
- package/manager/utils/importFresh.test.mjs +72 -0
- package/manager/utils/importFreshWorker.mjs +20 -0
- package/manager/utils/killTaggedBrowser.mjs +35 -0
- package/manager/utils/pagePathChanged.mjs +56 -0
- package/manager/utils/pruneRecordings.mjs +85 -0
- package/manager/utils/pruneRecordings.test.mjs +117 -0
- package/manager/utils/publishBuildDirectory.mjs +121 -0
- package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
- package/manager/utils/publishBuildDirectory.test.mjs +262 -0
- package/manager/utils/readBasePath.mjs +27 -0
- package/manager/utils/readBasePath.test.mjs +43 -0
- package/manager/utils/readBuildStatusWait.mjs +67 -0
- package/manager/utils/readManagerVersion.mjs +30 -0
- package/manager/utils/readManagerVersion.test.mjs +35 -0
- package/manager/utils/readPluginDefinitions.mjs +36 -0
- package/manager/utils/resolveDevAuthUrl.mjs +58 -0
- package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
- package/manager/utils/resolvePorts.mjs +49 -0
- package/manager/utils/setupWatcher.mjs +19 -11
- package/manager/utils/startRecordingPruner.mjs +45 -0
- package/manager/utils/startRecordingPruner.test.mjs +66 -0
- package/manager/utils/waitForChildExit.mjs +53 -0
- package/manager/utils/waitForChildExit.test.mjs +70 -0
- package/manager/utils/waitForServer.mjs +44 -0
- package/manager/utils/writeBuildStatus.mjs +1 -1
- package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
- package/manager/watchers/envWatcher.mjs +9 -2
- package/manager/watchers/envWatcher.test.mjs +87 -0
- package/manager/watchers/listServerSidePackages.mjs +47 -0
- package/manager/watchers/listServerSidePackages.test.mjs +38 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
- package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
- package/manager/watchers/pluginSourceWatcher.mjs +79 -0
- package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
- package/manager/watchers/restartRequestWatcher.mjs +71 -0
- package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
- package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
- package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
- package/manager/watchers/serverPackageWatcher.mjs +38 -0
- package/package.json +60 -47
- package/package.original.json +60 -47
- package/src/app.js +131 -20
- package/src/html/renderDevPage.js +63 -11
- package/src/html/renderDevPage.test.mjs +138 -0
- package/src/lib/getRequestPath.js +29 -0
- package/src/middleware/createSameOriginGuard.js +37 -0
- package/src/middleware/createSameOriginGuard.test.mjs +102 -0
- package/src/middleware/errorHandler.js +51 -1
- package/src/middleware/errorHandler.test.mjs +173 -3
- package/src/middleware/isRebindingSafeHost.js +44 -0
- package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
- package/src/middleware/localDevToolsOnly.js +38 -0
- package/src/middleware/localDevToolsOnly.test.mjs +61 -0
- package/src/middleware/noStore.js +27 -0
- package/src/middleware/noStore.test.mjs +48 -0
- package/src/middleware/requestTimeout.js +38 -0
- package/src/middleware/staleFlag.js +76 -0
- package/src/middleware/staleFlag.test.mjs +127 -0
- package/src/middleware/stampCaller.js +38 -0
- package/src/middleware/stampCaller.test.mjs +64 -0
- package/src/routes/agent.js +15 -3
- package/src/routes/auth.js +29 -20
- package/src/routes/auth.test.mjs +30 -56
- package/src/routes/clientError.js +13 -13
- package/src/routes/cron.js +3 -1
- package/src/routes/cronForward.js +3 -1
- package/src/routes/detached.js +22 -9
- package/src/routes/devInspect.js +17 -8
- package/src/routes/devInspect.test.mjs +44 -7
- package/src/routes/devRecording.js +113 -0
- package/src/routes/devRecording.test.mjs +253 -0
- package/src/routes/docs/buildStatus.js +10 -2
- package/src/routes/docs/buildStatus.test.mjs +46 -0
- package/src/routes/docs/content.js +5 -3
- package/src/routes/docs/evalOperator.js +15 -1
- package/src/routes/docs/events.js +50 -0
- package/src/routes/docs/events.test.mjs +117 -0
- package/src/routes/docs/find.js +9 -1
- package/src/routes/docs/icons.js +30 -0
- package/src/routes/docs/inspectState.js +16 -1
- package/src/routes/docs/journey.js +194 -0
- package/src/routes/docs/journey.test.mjs +401 -0
- package/src/routes/docs/journeyRefusals.test.mjs +71 -0
- package/src/routes/docs/loadState.js +4 -2
- package/src/routes/docs/mcp.js +34 -3
- package/src/routes/docs/mcp.test.mjs +130 -0
- package/src/routes/docs/mutants.js +78 -0
- package/src/routes/docs/mutants.test.mjs +180 -0
- package/src/routes/docs/parsePathParamsParam.js +41 -0
- package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
- package/src/routes/docs/parseUserParam.js +27 -21
- package/src/routes/docs/parseUserParam.test.mjs +17 -5
- package/src/routes/docs/restart.js +35 -0
- package/src/routes/docs/restart.test.mjs +64 -0
- package/src/routes/docs/runEndpoint.js +54 -0
- package/src/routes/docs/runEndpoint.test.mjs +165 -0
- package/src/routes/docs/runRequest.js +25 -8
- package/src/routes/docs/runRequest.test.mjs +92 -0
- package/src/routes/docs/screenshot.js +50 -2
- package/src/routes/docs/screenshot.test.mjs +117 -0
- package/src/routes/docs/search.js +15 -2
- package/src/routes/docs/snapshotState.js +17 -3
- package/src/routes/endpoints.js +27 -26
- package/src/routes/endpointsTextAnswer.test.mjs +77 -0
- package/src/routes/feedback.js +10 -13
- package/src/routes/jitPage.js +99 -35
- package/src/routes/jitPage.test.mjs +248 -0
- package/src/routes/mcp.js +82 -5
- package/src/routes/mcp.test.mjs +215 -0
- package/src/routes/mcpMemberToken.test.mjs +125 -0
- package/src/routes/mcpProtectedResourceMetadata.js +34 -0
- package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
- package/src/routes/mountDevPageRoutes.js +25 -0
- package/src/routes/mountDevPageRoutes.test.mjs +30 -0
- package/src/routes/mountOauthDiscovery.js +44 -0
- package/src/routes/mountOauthDiscovery.test.mjs +79 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
- package/src/routes/reload.js +16 -0
- package/src/routes/request.js +6 -4
- package/src/routes/request.test.mjs +9 -5
- package/src/routes/requestBody.test.mjs +159 -0
- package/src/routes/usage.js +6 -2
- package/src/routes/user.js +27 -0
- package/src/routes/websocket.js +29 -8
- package/src/routes/websocket.test.mjs +65 -0
- package/src/routes/wellKnownFallback.js +30 -0
- package/src/routes/wellKnownFallback.test.mjs +32 -0
- package/src/websocket/devWebSocket.js +41 -26
- package/src/websocket/devWebSocket.test.mjs +157 -0
- package/vite.config.js +53 -23
- package/lib/server/auth/getDevSession.js +0 -70
- package/lib/server/auth/getDevSession.test.mjs +0 -138
- package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
- package/manager/watchers/moduleBuildWatcher.mjs +0 -79
- package/manager/watchers/serverArtifactWatcher.mjs +0 -120
- package/src/routes/apiPage.js +0 -37
|
@@ -16,8 +16,15 @@
|
|
|
16
16
|
|
|
17
17
|
import { jest } from '@jest/globals';
|
|
18
18
|
|
|
19
|
-
const {
|
|
20
|
-
|
|
19
|
+
const {
|
|
20
|
+
findPageInstance,
|
|
21
|
+
listTabs,
|
|
22
|
+
registerTab,
|
|
23
|
+
requestFromTab,
|
|
24
|
+
resolveTabRequest,
|
|
25
|
+
unregisterTab,
|
|
26
|
+
updateTabPage,
|
|
27
|
+
} = await import('./tabChannel.js');
|
|
21
28
|
|
|
22
29
|
// tabChannel keeps its registries at module scope, so clear connected tabs
|
|
23
30
|
// between tests to keep them independent.
|
|
@@ -25,43 +32,139 @@ afterEach(() => {
|
|
|
25
32
|
listTabs().forEach((tab) => unregisterTab({ id: tab.id }));
|
|
26
33
|
});
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
function ticket({ space = 's', ticketId }) {
|
|
36
|
+
return {
|
|
37
|
+
pageId: 'ticket',
|
|
38
|
+
pathParams: { space, ticket_id: ticketId },
|
|
39
|
+
instanceKey: `page:ticket#tickets/${space}/${ticketId}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function connectTab({ id, pageId, send = jest.fn(), ...origin }) {
|
|
44
|
+
registerTab({ id, send, ...origin });
|
|
45
|
+
if (pageId) {
|
|
46
|
+
updateTabPage({ id, pageId, pathParams: {}, instanceKey: `page:${pageId}` });
|
|
47
|
+
}
|
|
48
|
+
return send;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('registerTab adds a tab visible via listTabs, on no page until its first page renders', () => {
|
|
52
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
30
53
|
const tabs = listTabs();
|
|
31
54
|
expect(tabs).toHaveLength(1);
|
|
32
|
-
expect(tabs[0]).toMatchObject({ id: 'tab-1', pageId:
|
|
55
|
+
expect(tabs[0]).toMatchObject({ id: 'tab-1', pageId: null, pathParams: null, instanceKey: null });
|
|
33
56
|
expect(tabs[0].connectedAt).toBeInstanceOf(Date);
|
|
34
57
|
});
|
|
35
58
|
|
|
36
59
|
test('registerTab throws when id is missing', () => {
|
|
37
|
-
expect(() => registerTab({
|
|
60
|
+
expect(() => registerTab({ send: jest.fn() })).toThrow('registerTab requires');
|
|
38
61
|
});
|
|
39
62
|
|
|
40
63
|
test('registerTab throws when send is not a function', () => {
|
|
41
|
-
expect(() => registerTab({ id: 'tab-1'
|
|
42
|
-
'registerTab requires a "send" function'
|
|
43
|
-
);
|
|
64
|
+
expect(() => registerTab({ id: 'tab-1' })).toThrow('registerTab requires a "send" function');
|
|
44
65
|
});
|
|
45
66
|
|
|
46
67
|
test('unregisterTab removes a tab from the registry', () => {
|
|
47
|
-
|
|
68
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
48
69
|
unregisterTab({ id: 'tab-1' });
|
|
49
70
|
expect(listTabs()).toHaveLength(0);
|
|
50
71
|
});
|
|
51
72
|
|
|
52
|
-
test('updateTabPage
|
|
53
|
-
|
|
54
|
-
updateTabPage({ id: 'tab-1',
|
|
55
|
-
expect(listTabs()[0]
|
|
73
|
+
test('updateTabPage records the page instance a tab shows', () => {
|
|
74
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
75
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '2' }) });
|
|
76
|
+
expect(listTabs()[0]).toMatchObject({
|
|
77
|
+
id: 'tab-1',
|
|
78
|
+
pageId: 'ticket',
|
|
79
|
+
pathParams: { space: 's', ticket_id: '2' },
|
|
80
|
+
instanceKey: 'page:ticket#tickets/s/2',
|
|
81
|
+
});
|
|
56
82
|
});
|
|
57
83
|
|
|
58
84
|
test('updateTabPage on an unknown tab id does not throw', () => {
|
|
59
|
-
expect(() =>
|
|
85
|
+
expect(() =>
|
|
86
|
+
updateTabPage({ id: 'missing', pageId: 'about', instanceKey: 'page:about' })
|
|
87
|
+
).not.toThrow();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('updateTabPage throws when the instance key is missing', () => {
|
|
91
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
92
|
+
expect(() => updateTabPage({ id: 'tab-1', pageId: 'about' })).toThrow(
|
|
93
|
+
'updateTabPage requires "pageId" and "instanceKey" strings'
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('findPageInstance reads the instance on screen when the tab is on the page', () => {
|
|
98
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
99
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
100
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '2' }) });
|
|
101
|
+
const { tab, ...instance } = findPageInstance({ pageId: 'ticket' });
|
|
102
|
+
expect(tab.id).toEqual('tab-1');
|
|
103
|
+
expect(instance).toEqual(ticket({ ticketId: '2' }));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('findPageInstance reads the most recently rendered instance when no tab is on the page', () => {
|
|
107
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
108
|
+
registerTab({ id: 'tab-2', send: jest.fn() });
|
|
109
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
110
|
+
updateTabPage({ id: 'tab-2', ...ticket({ ticketId: '2' }) });
|
|
111
|
+
updateTabPage({ id: 'tab-1', pageId: 'home', instanceKey: 'page:home' });
|
|
112
|
+
updateTabPage({ id: 'tab-2', pageId: 'home', instanceKey: 'page:home' });
|
|
113
|
+
const { tab, ...instance } = findPageInstance({ pageId: 'ticket' });
|
|
114
|
+
expect(tab.id).toEqual('tab-2');
|
|
115
|
+
expect(instance).toEqual(ticket({ ticketId: '2' }));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('findPageInstance prefers a tab showing the page over a later render elsewhere', () => {
|
|
119
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
120
|
+
registerTab({ id: 'tab-2', send: jest.fn() });
|
|
121
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
122
|
+
updateTabPage({ id: 'tab-2', ...ticket({ ticketId: '2' }) });
|
|
123
|
+
updateTabPage({ id: 'tab-2', pageId: 'home', instanceKey: 'page:home' });
|
|
124
|
+
const { tab, ...instance } = findPageInstance({ pageId: 'ticket' });
|
|
125
|
+
expect(tab.id).toEqual('tab-1');
|
|
126
|
+
expect(instance).toEqual(ticket({ ticketId: '1' }));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('findPageInstance with pathParams reads that instance, comparing values as strings', () => {
|
|
130
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
131
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
132
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '2' }) });
|
|
133
|
+
const { tab, ...instance } = findPageInstance({
|
|
134
|
+
pageId: 'ticket',
|
|
135
|
+
pathParams: { space: 's', ticket_id: 1 },
|
|
136
|
+
});
|
|
137
|
+
expect(tab.id).toEqual('tab-1');
|
|
138
|
+
expect(instance).toEqual(ticket({ ticketId: '1' }));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('findPageInstance returns undefined for values no tab has rendered', () => {
|
|
142
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
143
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
144
|
+
expect(
|
|
145
|
+
findPageInstance({ pageId: 'ticket', pathParams: { space: 's', ticket_id: '3' } })
|
|
146
|
+
).toBeUndefined();
|
|
147
|
+
expect(findPageInstance({ pageId: 'ticket', pathParams: { space: 's' } })).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('findPageInstance without a pageId reads the most recently connected tab on screen', () => {
|
|
151
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
152
|
+
connectTab({ id: 'tab-2', pageId: 'about' });
|
|
153
|
+
registerTab({ id: 'tab-3', send: jest.fn() });
|
|
154
|
+
const { tab, ...instance } = findPageInstance();
|
|
155
|
+
expect(tab.id).toEqual('tab-2');
|
|
156
|
+
expect(instance).toEqual({ pageId: 'about', pathParams: {}, instanceKey: 'page:about' });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('findPageInstance forgets the instances of a tab that disconnected', () => {
|
|
160
|
+
registerTab({ id: 'tab-1', send: jest.fn() });
|
|
161
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
162
|
+
unregisterTab({ id: 'tab-1' });
|
|
163
|
+
expect(findPageInstance({ pageId: 'ticket' })).toBeUndefined();
|
|
60
164
|
});
|
|
61
165
|
|
|
62
166
|
test('requestFromTab sends the event to the matching tab and resolves via resolveTabRequest', async () => {
|
|
63
|
-
const send =
|
|
64
|
-
registerTab({ id: 'tab-1', pageId: 'home', send });
|
|
167
|
+
const send = connectTab({ id: 'tab-1', pageId: 'home' });
|
|
65
168
|
const promise = requestFromTab({ pageId: 'home', event: 'inspect-request' });
|
|
66
169
|
|
|
67
170
|
expect(send).toHaveBeenCalledTimes(1);
|
|
@@ -74,11 +177,28 @@ test('requestFromTab sends the event to the matching tab and resolves via resolv
|
|
|
74
177
|
await expect(promise).resolves.toEqual('snapshot');
|
|
75
178
|
});
|
|
76
179
|
|
|
77
|
-
test('requestFromTab
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
180
|
+
test('requestFromTab names the instance to read in the event it sends', async () => {
|
|
181
|
+
const send = jest.fn();
|
|
182
|
+
registerTab({ id: 'tab-1', send });
|
|
183
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '1' }) });
|
|
184
|
+
updateTabPage({ id: 'tab-1', ...ticket({ ticketId: '2' }) });
|
|
185
|
+
requestFromTab({
|
|
186
|
+
pageId: 'ticket',
|
|
187
|
+
pathParams: { space: 's', ticket_id: '1' },
|
|
188
|
+
event: 'eval-request',
|
|
189
|
+
payload: { expression: { _state: 'a' } },
|
|
190
|
+
timeout: 10,
|
|
191
|
+
});
|
|
192
|
+
expect(send.mock.calls[0][1]).toEqual({
|
|
193
|
+
requestId: expect.any(String),
|
|
194
|
+
...ticket({ ticketId: '1' }),
|
|
195
|
+
expression: { _state: 'a' },
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('requestFromTab targets the tab on the given pageId', async () => {
|
|
200
|
+
const sendHome = connectTab({ id: 'tab-home', pageId: 'home' });
|
|
201
|
+
const sendAbout = connectTab({ id: 'tab-about', pageId: 'about' });
|
|
82
202
|
|
|
83
203
|
const promise = requestFromTab({ pageId: 'about', event: 'eval-request' });
|
|
84
204
|
expect(sendAbout).toHaveBeenCalledTimes(1);
|
|
@@ -90,9 +210,8 @@ test('requestFromTab targets the tab registered for the given pageId', async ()
|
|
|
90
210
|
});
|
|
91
211
|
|
|
92
212
|
test('requestFromTab picks the most recently registered tab when pageId is omitted', async () => {
|
|
93
|
-
|
|
94
|
-
const sendLatest =
|
|
95
|
-
registerTab({ id: 'tab-2', pageId: 'about', send: sendLatest });
|
|
213
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
214
|
+
const sendLatest = connectTab({ id: 'tab-2', pageId: 'about' });
|
|
96
215
|
|
|
97
216
|
const promise = requestFromTab({ event: 'inspect-request' });
|
|
98
217
|
expect(sendLatest).toHaveBeenCalledTimes(1);
|
|
@@ -103,13 +222,25 @@ test('requestFromTab picks the most recently registered tab when pageId is omitt
|
|
|
103
222
|
});
|
|
104
223
|
|
|
105
224
|
test('requestFromTab resolves with an error when no tab matches the pageId', async () => {
|
|
106
|
-
|
|
225
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
107
226
|
const response = await requestFromTab({ pageId: 'missing-page', event: 'inspect-request' });
|
|
108
227
|
expect(response.error).toContain('No browser tab connected');
|
|
109
228
|
});
|
|
110
229
|
|
|
230
|
+
test('requestFromTab names the pathParams in its error when no tab has that instance', async () => {
|
|
231
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
232
|
+
const response = await requestFromTab({
|
|
233
|
+
pageId: 'ticket',
|
|
234
|
+
pathParams: { space: 's', ticket_id: '1' },
|
|
235
|
+
event: 'inspect-request',
|
|
236
|
+
});
|
|
237
|
+
expect(response.error).toEqual(
|
|
238
|
+
'No browser tab connected on page "ticket" with pathParams {"space":"s","ticket_id":"1"}. Ask the developer to open the page, or use source: "headless".'
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
111
242
|
test('requestFromTab resolves with a timeout error when no response arrives in time', async () => {
|
|
112
|
-
|
|
243
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
113
244
|
const response = await requestFromTab({ pageId: 'home', event: 'inspect-request', timeout: 10 });
|
|
114
245
|
expect(response.error).toContain('Timed out');
|
|
115
246
|
});
|
|
@@ -119,8 +250,7 @@ test('resolveTabRequest returns false for an unknown requestId', () => {
|
|
|
119
250
|
});
|
|
120
251
|
|
|
121
252
|
test('resolveTabRequest returns false when called again for an already-settled requestId', async () => {
|
|
122
|
-
const send =
|
|
123
|
-
registerTab({ id: 'tab-1', pageId: 'home', send });
|
|
253
|
+
const send = connectTab({ id: 'tab-1', pageId: 'home' });
|
|
124
254
|
const promise = requestFromTab({ pageId: 'home', event: 'inspect-request' });
|
|
125
255
|
const requestId = send.mock.calls[0][1].requestId;
|
|
126
256
|
|
|
@@ -128,3 +258,31 @@ test('resolveTabRequest returns false when called again for an already-settled r
|
|
|
128
258
|
await promise;
|
|
129
259
|
expect(resolveTabRequest({ requestId, result: 'second' })).toBe(false);
|
|
130
260
|
});
|
|
261
|
+
|
|
262
|
+
test('requestFromTab skips an automated journey tab for the developer tab on the same page', async () => {
|
|
263
|
+
const developerSend = connectTab({ id: 'developer', pageId: 'tickets' });
|
|
264
|
+
const walkSend = connectTab({
|
|
265
|
+
id: 'walk',
|
|
266
|
+
pageId: 'tickets',
|
|
267
|
+
source: 'journey',
|
|
268
|
+
automated: true,
|
|
269
|
+
});
|
|
270
|
+
const promise = requestFromTab({ pageId: 'tickets', event: 'inspect-request', timeout: 50 });
|
|
271
|
+
expect(developerSend).toHaveBeenCalledTimes(1);
|
|
272
|
+
expect(walkSend).not.toHaveBeenCalled();
|
|
273
|
+
const [, { requestId }] = developerSend.mock.calls[0];
|
|
274
|
+
resolveTabRequest({ requestId, result: { state: {} } });
|
|
275
|
+
await expect(promise).resolves.toEqual({ state: {} });
|
|
276
|
+
expect(listTabs().map(({ id, source, automated }) => ({ id, source, automated }))).toEqual([
|
|
277
|
+
{ id: 'developer', source: 'dev', automated: false },
|
|
278
|
+
{ id: 'walk', source: 'journey', automated: true },
|
|
279
|
+
]);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('requestFromTab finds no tab when only an automated tab is on the page', async () => {
|
|
283
|
+
connectTab({ id: 'walk', pageId: 'tickets', source: 'journey', automated: true });
|
|
284
|
+
await expect(requestFromTab({ pageId: 'tickets', event: 'inspect-request' })).resolves.toEqual({
|
|
285
|
+
error:
|
|
286
|
+
'No browser tab connected on page "tickets". Ask the developer to open the page, or use source: "headless".',
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The note every headless tool attaches alongside `ready: false`, so an agent
|
|
18
|
+
// reading the result knows it is looking at a page mid-flight rather than the
|
|
19
|
+
// page's settled truth.
|
|
20
|
+
function unsettledPageNote({ timeout }) {
|
|
21
|
+
return `Page did not settle within ${timeout}ms — onMountAsync, a request or a subscription is still running. The result below is a snapshot of an unsettled page.`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default unsettledPageNote;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
import { getStepKey } from '@lowdefy/node-utils';
|
|
19
|
+
|
|
20
|
+
function readsMail(step) {
|
|
21
|
+
const key = getStepKey(step);
|
|
22
|
+
return key === 'email' || (key === 'fill' && !type.isUndefined(step.fill.fromEmail));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Steps that read email need the dev server's mail sink. Without it they would
|
|
26
|
+
// each wait out their timeout for mail that can never arrive, so the journey is
|
|
27
|
+
// refused up front. Returns an error message, or undefined.
|
|
28
|
+
function validateJourneyMail({ steps }) {
|
|
29
|
+
const capturesMail = process.env.LOWDEFY_SERVER_DEV_MAIL_SINK === 'true';
|
|
30
|
+
if (steps.some(readsMail) && !capturesMail) {
|
|
31
|
+
return 'The steps read email (an "email" step or a "fill" with "fromEmail"), but this dev server captures no mail. Start (or restart) it with LOWDEFY_DEV_SMTP_PORT set to a free port, and point the app\'s SMTP connection at 127.0.0.1 on that port.';
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default validateJourneyMail;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
// A step that needs longer than a minute is a hung page, and a failing journey
|
|
20
|
+
// should report in minutes, not hours.
|
|
21
|
+
const MAX_JOURNEY_TIMEOUT = 60000;
|
|
22
|
+
|
|
23
|
+
// The journey's `timeout`: how long each step may wait, in milliseconds.
|
|
24
|
+
// Returns an error message, or undefined when it is omitted or well-formed.
|
|
25
|
+
function validateJourneyTimeout({ timeout }) {
|
|
26
|
+
if (type.isUndefined(timeout)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (!type.isInt(timeout) || timeout < 1 || timeout > MAX_JOURNEY_TIMEOUT) {
|
|
30
|
+
return `The journey "timeout" must be a whole number of milliseconds from 1 to ${MAX_JOURNEY_TIMEOUT}. Received ${JSON.stringify(
|
|
31
|
+
timeout
|
|
32
|
+
)}.`;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { MAX_JOURNEY_TIMEOUT };
|
|
38
|
+
export default validateJourneyTimeout;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import validateJourneyTimeout from './validateJourneyTimeout.js';
|
|
18
|
+
|
|
19
|
+
test.each([undefined, 1, 5000, 60000])('validateJourneyTimeout accepts %j', (timeout) => {
|
|
20
|
+
expect(validateJourneyTimeout({ timeout })).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test.each([0, -1, 60001, 1500.5, '5000', null])('validateJourneyTimeout rejects %j', (timeout) => {
|
|
24
|
+
expect(validateJourneyTimeout({ timeout })).toEqual(
|
|
25
|
+
`The journey "timeout" must be a whole number of milliseconds from 1 to 60000. Received ${JSON.stringify(
|
|
26
|
+
timeout
|
|
27
|
+
)}.`
|
|
28
|
+
);
|
|
29
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { isTraceId, type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
// The journey route's optional `recording` param, { run, journey }: the test
|
|
20
|
+
// run (a trace id) this journey records into, and the journey's name in it.
|
|
21
|
+
// Returns an error message, or undefined when the param is absent or valid.
|
|
22
|
+
function validateRecordingParam(recording) {
|
|
23
|
+
if (type.isNone(recording)) return undefined;
|
|
24
|
+
if (!type.isObject(recording)) {
|
|
25
|
+
return `The "recording" param must be an object { run, journey }. Received ${JSON.stringify(
|
|
26
|
+
recording
|
|
27
|
+
)}.`;
|
|
28
|
+
}
|
|
29
|
+
if (!isTraceId(recording.run)) {
|
|
30
|
+
return `The "recording.run" param must be a trace id like "20261003T151200Z-p0d4rm". Received ${JSON.stringify(
|
|
31
|
+
recording.run
|
|
32
|
+
)}.`;
|
|
33
|
+
}
|
|
34
|
+
if (!type.isNone(recording.journey) && !type.isString(recording.journey)) {
|
|
35
|
+
return `The "recording.journey" param must be a string or null. Received ${JSON.stringify(
|
|
36
|
+
recording.journey
|
|
37
|
+
)}.`;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default validateRecordingParam;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
// A journey's `state` option: true, false, or an array of state paths.
|
|
20
|
+
// Returns an error message, or undefined when it is valid or not given.
|
|
21
|
+
function validateStateSelection({ state }) {
|
|
22
|
+
if (type.isUndefined(state) || type.isBoolean(state)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (type.isArray(state) && state.every((path) => type.isString(path) && path !== '')) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return `The "state" option must be true, false or an array of state paths such as ["form.name"]. Received ${JSON.stringify(
|
|
29
|
+
state
|
|
30
|
+
)}.`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default validateStateSelection;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import validateStateSelection from './validateStateSelection.js';
|
|
18
|
+
|
|
19
|
+
test.each([undefined, true, false, [], ['form.name', 'rows']])(
|
|
20
|
+
'validateStateSelection accepts %j',
|
|
21
|
+
(state) => {
|
|
22
|
+
expect(validateStateSelection({ state })).toBeUndefined();
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
test.each(['all', null, 1, [''], ['a', 2], { path: 'a' }])(
|
|
27
|
+
'validateStateSelection rejects %j',
|
|
28
|
+
(state) => {
|
|
29
|
+
expect(validateStateSelection({ state })).toMatch(
|
|
30
|
+
/^The "state" option must be true, false or an array of state paths/
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
const COLOR_SCHEMES = ['light', 'dark'];
|
|
20
|
+
|
|
21
|
+
// Wide enough for a 4K screen. Chromium renders far larger viewports only
|
|
22
|
+
// slowly, and fails some of them after tying up the browser for many seconds;
|
|
23
|
+
// a full-page screenshot, not a tall viewport, is the way to see a long page.
|
|
24
|
+
const MAX_VIEWPORT_SIZE = 4096;
|
|
25
|
+
|
|
26
|
+
function isViewportSize(value) {
|
|
27
|
+
return type.isInt(value) && value > 0 && value <= MAX_VIEWPORT_SIZE;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Checks the viewport a headless page is opened with: width and height in CSS
|
|
31
|
+
// pixels, and the colour scheme the page's `prefers-color-scheme` reports.
|
|
32
|
+
// Returns an error message, or undefined when every given option is valid.
|
|
33
|
+
function validateViewport({ width, height, colorScheme }) {
|
|
34
|
+
if (!type.isUndefined(width) && !isViewportSize(width)) {
|
|
35
|
+
return `Viewport width must be a positive integer of at most ${MAX_VIEWPORT_SIZE} (CSS pixels). Received ${JSON.stringify(
|
|
36
|
+
width
|
|
37
|
+
)}.`;
|
|
38
|
+
}
|
|
39
|
+
if (!type.isUndefined(height) && !isViewportSize(height)) {
|
|
40
|
+
return `Viewport height must be a positive integer of at most ${MAX_VIEWPORT_SIZE} (CSS pixels). Received ${JSON.stringify(
|
|
41
|
+
height
|
|
42
|
+
)}.`;
|
|
43
|
+
}
|
|
44
|
+
if (!type.isUndefined(colorScheme) && !COLOR_SCHEMES.includes(colorScheme)) {
|
|
45
|
+
return `Color scheme must be "light" or "dark". Received ${JSON.stringify(colorScheme)}.`;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { COLOR_SCHEMES, MAX_VIEWPORT_SIZE };
|
|
51
|
+
export default validateViewport;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import validateViewport from './validateViewport.js';
|
|
18
|
+
|
|
19
|
+
test('validateViewport accepts no options and every valid option', () => {
|
|
20
|
+
expect(validateViewport({})).toBeUndefined();
|
|
21
|
+
expect(validateViewport({ width: 390, height: 844, colorScheme: 'dark' })).toBeUndefined();
|
|
22
|
+
expect(validateViewport({ colorScheme: 'light' })).toBeUndefined();
|
|
23
|
+
expect(validateViewport({ width: 4096, height: 4096 })).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test.each([
|
|
27
|
+
[
|
|
28
|
+
{ width: 0 },
|
|
29
|
+
'Viewport width must be a positive integer of at most 4096 (CSS pixels). Received 0.',
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
{ width: 390.5 },
|
|
33
|
+
'Viewport width must be a positive integer of at most 4096 (CSS pixels). Received 390.5.',
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
{ height: -1 },
|
|
37
|
+
'Viewport height must be a positive integer of at most 4096 (CSS pixels). Received -1.',
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
{ height: '800' },
|
|
41
|
+
'Viewport height must be a positive integer of at most 4096 (CSS pixels). Received "800".',
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
{ width: 40000 },
|
|
45
|
+
'Viewport width must be a positive integer of at most 4096 (CSS pixels). Received 40000.',
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
{ height: 4097 },
|
|
49
|
+
'Viewport height must be a positive integer of at most 4096 (CSS pixels). Received 4097.',
|
|
50
|
+
],
|
|
51
|
+
[{ colorScheme: 'dim' }, 'Color scheme must be "light" or "dark". Received "dim".'],
|
|
52
|
+
])('validateViewport rejects %j', (options, message) => {
|
|
53
|
+
expect(validateViewport(options)).toEqual(message);
|
|
54
|
+
});
|