@wenathlan/devthink 2.0.13 → 2.0.15
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/CHANGELOG.md +27 -0
- package/accounts.ts +2087 -0
- package/agent.ts +2331 -0
- package/antigravity.ts +2139 -0
- package/apifreeze.ts +4146 -0
- package/auth.ts +642 -0
- package/background.ts +16837 -0
- package/bridge.ts +2080 -0
- package/build.ts +54 -0
- package/bun.ts +26 -0
- package/capture.ts +1614 -0
- package/checksecrets.ts +72 -0
- package/cjs.ts +9 -0
- package/cli.ts +3500 -0
- package/commands.ts +1101 -0
- package/companion.ts +450 -0
- package/compatibility.ts +37 -0
- package/config.ts +3137 -0
- package/constants.ts +943 -0
- package/core.ts +1473 -0
- package/crossbrowser.ts +1359 -0
- package/dashdone.ts +605 -0
- package/data.ts +943 -0
- package/database.ts +133 -0
- package/debug.ts +850 -0
- package/deno.ts +27 -0
- package/devthink.ts +707 -0
- package/dist/accounts.d.ts +409 -0
- package/dist/agent.d.ts +837 -0
- package/dist/antigravity.d.ts +380 -0
- package/dist/apifreeze.d.ts +113 -0
- package/dist/auth.d.ts +224 -0
- package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
- package/dist/capture.d.ts +595 -0
- package/dist/cjs.d.ts +6 -0
- package/{cli.js → dist/cli.js} +2 -2
- package/dist/commands.d.ts +310 -0
- package/dist/compatibility.d.ts +17 -0
- package/dist/config.d.ts +1853 -0
- package/dist/constants.d.ts +637 -0
- package/dist/core.d.ts +361 -0
- package/dist/data.d.ts +330 -0
- package/dist/database.d.ts +24 -0
- package/dist/debug.d.ts +270 -0
- package/dist/engine.d.ts +74 -0
- package/dist/environments.d.ts +244 -0
- package/dist/evidence.d.ts +277 -0
- package/dist/export.d.ts +311 -0
- package/dist/fingerprint.d.ts +594 -0
- package/dist/flow.d.ts +300 -0
- package/dist/gates.d.ts +103 -0
- package/dist/identity.d.ts +51 -0
- package/dist/ids.d.ts +7 -0
- package/dist/llm.d.ts +652 -0
- package/dist/models.d.ts +1807 -0
- package/dist/net.d.ts +325 -0
- package/dist/neutral.d.ts +41 -0
- package/dist/oauth.d.ts +703 -0
- package/dist/page.d.ts +1166 -0
- package/dist/perf.d.ts +481 -0
- package/dist/plan.d.ts +134 -0
- package/dist/project.d.ts +395 -0
- package/dist/providers.d.ts +37 -0
- package/dist/quota.d.ts +453 -0
- package/dist/recovery.d.ts +621 -0
- package/dist/request.d.ts +1492 -0
- package/dist/run.d.ts +779 -0
- package/dist/runtime.d.ts +212 -0
- package/dist/search.d.ts +375 -0
- package/dist/security.d.ts +534 -0
- package/dist/serve.d.ts +136 -0
- package/dist/session.d.ts +296 -0
- package/dist/storage.d.ts +53 -0
- package/dist/streaming.d.ts +1119 -0
- package/dist/swarm.d.ts +328 -0
- package/dist/tools.d.ts +170 -0
- package/dist/types.d.ts +8357 -0
- package/dist/utils.d.ts +82 -0
- package/dist/version.d.ts +7 -0
- package/dist/versionregistry.d.ts +406 -0
- package/dist/views.d.ts +579 -0
- package/dist/workbenchsession.d.ts +69 -0
- package/dist/workflow.d.ts +649 -0
- package/docs/readme.md +92 -0
- package/docs/schemas/antigravity.json +192 -0
- package/engine.ts +1524 -0
- package/environments.ts +1005 -0
- package/evidence.ts +643 -0
- package/export.ts +889 -0
- package/fingerprint.ts +1458 -0
- package/flow.ts +1190 -0
- package/gates.ts +307 -0
- package/gateway.ts +34 -0
- package/hardening.ts +191 -0
- package/headless.ts +369 -0
- package/http.ts +2110 -0
- package/identity.ts +246 -0
- package/ids.ts +26 -0
- package/index.ts +369 -0
- package/llm.ts +2682 -0
- package/mcp.ts +1835 -0
- package/{memory.js → memory.ts} +3834 -2310
- package/models.ts +3805 -0
- package/modes.ts +43 -0
- package/net.ts +1214 -0
- package/neutral.ts +362 -0
- package/node.ts +26 -0
- package/oauth.ts +2216 -0
- package/offscreen.ts +170 -0
- package/pack.ts +956 -0
- package/package.json +175 -82
- package/page.ts +7824 -0
- package/pagebridge.ts +1116 -0
- package/perf.ts +957 -0
- package/plan.ts +672 -0
- package/plugin.ts +76 -0
- package/{policy.js → policy.ts} +5073 -4679
- package/prisma.config.ts +27 -0
- package/progress.ts +1308 -0
- package/project.ts +1291 -0
- package/protocol.ts +4187 -0
- package/providers.ts +246 -0
- package/quota.ts +1887 -0
- package/recovery.ts +2433 -0
- package/request.ts +4482 -0
- package/run.ts +2135 -0
- package/runtime.ts +526 -0
- package/search.ts +1760 -0
- package/security.ts +1673 -0
- package/serve.ts +419 -0
- package/server.ts +5538 -0
- package/session.ts +1162 -0
- package/storage.ts +182 -0
- package/streaming.ts +4101 -0
- package/swarm.ts +884 -0
- package/sync.ts +56 -0
- package/tests/agentcert.test.ts +128 -0
- package/tests/agentfleet.test.ts +214 -0
- package/tests/agentreview.test.ts +241 -0
- package/tests/agentstream.test.ts +335 -0
- package/tests/agentwork.test.ts +356 -0
- package/tests/apifreeze.test.ts +290 -0
- package/tests/apimap.test.ts +112 -0
- package/tests/approvalgate.test.ts +175 -0
- package/tests/artifactmanifest.test.ts +204 -0
- package/tests/attentionfeed.test.ts +163 -0
- package/tests/backgroundruns.test.ts +100 -0
- package/tests/batchscheduling.test.ts +131 -0
- package/tests/blackboard.test.ts +238 -0
- package/tests/bridge.test.ts +1088 -0
- package/tests/browsercoverage.test.ts +134 -0
- package/tests/browserpolyfills.test.ts +124 -0
- package/tests/build.mjs +1260 -0
- package/tests/clientauth.test.ts +223 -0
- package/tests/clitools.test.ts +578 -0
- package/tests/confirmgates.test.ts +190 -0
- package/tests/containerpack.test.ts +143 -0
- package/tests/controlflow.test.ts +2120 -0
- package/tests/coordination.test.ts +345 -0
- package/tests/copyscreen.test.ts +139 -0
- package/tests/core.test.ts +417 -0
- package/tests/costcert.test.ts +95 -0
- package/tests/crossbrowser.test.ts +115 -0
- package/tests/cspaudit.test.ts +79 -0
- package/tests/dashdone.test.ts +615 -0
- package/tests/datagrid.test.ts +137 -0
- package/tests/doccheck.test.ts +107 -0
- package/tests/dombatch.test.ts +109 -0
- package/tests/emulation.test.ts +282 -0
- package/tests/environments.test.ts +259 -0
- package/tests/evidenceviews.test.ts +88 -0
- package/tests/exporttools.test.ts +185 -0
- package/tests/firefoxprep.test.ts +134 -0
- package/tests/flowlibrary.test.ts +242 -0
- package/tests/flowrun.test.ts +303 -0
- package/tests/forensics.test.ts +439 -0
- package/tests/gateway.test.ts +939 -0
- package/tests/headlesslib.test.ts +171 -0
- package/tests/httpstream.test.ts +277 -0
- package/tests/immutablelog.test.ts +157 -0
- package/tests/inboundguard.test.ts +177 -0
- package/tests/lazyload.test.ts +72 -0
- package/tests/librarymodes.test.ts +623 -0
- package/tests/llm.test.ts +696 -0
- package/tests/manifest.test.ts +226 -0
- package/tests/maskinputs.test.ts +150 -0
- package/tests/matrixverify.test.ts +111 -0
- package/tests/mavenpack.test.ts +75 -0
- package/tests/mcpserver.test.ts +704 -0
- package/tests/memory.test.ts +4155 -0
- package/tests/memorycare.test.ts +230 -0
- package/tests/migrateplan.test.ts +349 -0
- package/tests/minimization.test.ts +414 -0
- package/tests/modelroute.test.ts +112 -0
- package/tests/multiagent.test.ts +440 -0
- package/tests/nativebridge.test.ts +807 -0
- package/tests/navigation.test.ts +284 -0
- package/tests/nugetpack.test.ts +83 -0
- package/tests/orchestration.test.ts +449 -0
- package/tests/originpolicy.test.ts +405 -0
- package/tests/outputcompare.test.ts +97 -0
- package/tests/package.test.ts +91 -0
- package/tests/pagebridge.test.ts +5909 -0
- package/tests/pentest.test.ts +75 -0
- package/tests/perfrecords.test.ts +257 -0
- package/tests/permdiff.test.ts +85 -0
- package/tests/phishguard.test.ts +128 -0
- package/tests/pickerviews.test.ts +155 -0
- package/tests/pipelines.test.ts +381 -0
- package/tests/planlint.test.ts +240 -0
- package/tests/planreview.test.ts +383 -0
- package/tests/platformtargets.test.ts +56 -0
- package/tests/policy.test.ts +8284 -0
- package/tests/portability.test.ts +96 -0
- package/tests/progress.test.ts +1036 -0
- package/tests/promptlibrary.test.ts +116 -0
- package/tests/protocol.test.ts +4793 -0
- package/tests/publishingpipeline.test.ts +314 -0
- package/tests/quickactions.test.ts +164 -0
- package/tests/readiness.test.ts +78 -0
- package/tests/recipes.test.ts +163 -0
- package/tests/redactshots.test.ts +216 -0
- package/tests/registernvidiakeys.ts +51 -0
- package/tests/relayserve.test.ts +304 -0
- package/tests/releasenotes.test.ts +276 -0
- package/tests/resilience.test.ts +467 -0
- package/tests/resourceaware.test.ts +187 -0
- package/tests/runbudget.test.ts +125 -0
- package/tests/runhistory.test.ts +299 -0
- package/tests/runreplay.test.ts +113 -0
- package/tests/runresume.test.ts +184 -0
- package/tests/runstate.test.ts +288 -0
- package/tests/runtimeadapters.test.ts +251 -0
- package/tests/safariskeleton.test.ts +115 -0
- package/tests/sandboxframe.test.ts +196 -0
- package/tests/sbom.test.ts +105 -0
- package/tests/secretvault.test.ts +188 -0
- package/tests/selcache.test.ts +111 -0
- package/tests/sessioninterface.test.ts +791 -0
- package/tests/sessions.test.ts +591 -0
- package/tests/siteprefs.test.ts +228 -0
- package/tests/snapshotdelta.test.ts +165 -0
- package/tests/statusviews.test.ts +237 -0
- package/tests/stepmeter.test.ts +200 -0
- package/tests/streamparse.test.ts +163 -0
- package/tests/stylepruning.test.ts +90 -0
- package/tests/surfaces.test.ts +282 -0
- package/tests/sweep.test.ts +78 -0
- package/tests/syncbridge.test.ts +164 -0
- package/tests/taskqueue.test.ts +206 -0
- package/tests/toolcalls.test.ts +336 -0
- package/tests/toolcatalog.test.ts +201 -0
- package/tests/torture-agent.test.ts +706 -0
- package/tests/torture-auth.test.ts +461 -0
- package/tests/torture-commands.test.ts +895 -0
- package/tests/torture-data.test.ts +923 -0
- package/tests/torture-environments.test.ts +285 -0
- package/tests/torture-export.test.ts +263 -0
- package/tests/torture-http.test.ts +1379 -0
- package/tests/torture-llm.test.ts +808 -0
- package/tests/torture-memory.test.ts +1084 -0
- package/tests/torture-plan.test.ts +829 -0
- package/tests/torture-run.test.ts +626 -0
- package/tests/torture-session.test.ts +928 -0
- package/tests/torture-swarm.test.ts +1245 -0
- package/tests/torture-tools.test.ts +964 -0
- package/tests/torture-workflow.test.ts +2354 -0
- package/tests/tourviews.test.ts +52 -0
- package/tests/transparency.test.ts +147 -0
- package/tests/trigger.test.ts +941 -0
- package/tests/v1sunset.test.ts +428 -0
- package/tests/virtlist.test.ts +94 -0
- package/tests/vision.test.ts +540 -0
- package/tests/vsixpack.test.ts +145 -0
- package/tests/webapi.test.ts +523 -0
- package/tests/workflow.test.ts +1785 -0
- package/tests/workfloweditor.test.ts +759 -0
- package/tests/xpipack.test.ts +128 -0
- package/tools.ts +734 -0
- package/types.ts +9908 -0
- package/umd.ts +9 -0
- package/utils.ts +249 -0
- package/version.ts +8 -0
- package/versionregistry.ts +1310 -0
- package/views.ts +2153 -0
- package/vitest.config.ts +12 -0
- package/web/App.tsx +78 -0
- package/web/README.md +47 -0
- package/web/capacitor.config.ts +46 -0
- package/web/const.ts +19 -0
- package/web/control.shell.tsx +66 -0
- package/web/dashboardpage.ts +554 -0
- package/web/db.ts +493 -0
- package/web/design.html +541 -0
- package/web/gatewayview/Gateway.tsx +345 -0
- package/web/gatewayview/config.ts +1042 -0
- package/web/gatewayview/definition.ts +174 -0
- package/web/icons.ts +15 -0
- package/{manifest.json → web/manifest.json} +1 -1
- package/web/optionspage.ts +691 -0
- package/web/popup.ts +1651 -0
- package/web/schema.prisma +176 -0
- package/web/sidepanel.ts +7941 -0
- package/web/sitemanifest.json +13 -0
- package/web/terminal-ui.tsx +622 -0
- package/web/transparencypage.ts +541 -0
- package/web/vite.config.ts +39 -0
- package/web/workspace.ts +23 -0
- package/workbenchmemory.ts +104 -0
- package/workbenchsession.ts +304 -0
- package/workflow.ts +4495 -0
- package/bridge.d.ts.map +0 -1
- package/bridge.js +0 -1877
- package/bun.d.ts.map +0 -1
- package/bun.js +0 -52329
- package/caps/background.json +0 -712
- package/caps/cli.json +0 -360
- package/caps/library.json +0 -3611
- package/caps/mcp.json +0 -126
- package/caps/pagebridge.json +0 -377
- package/caps/popup.json +0 -51
- package/caps/sidepanel.json +0 -522
- package/checksums.txt +0 -142
- package/cli.d.ts.map +0 -1
- package/companion.d.ts.map +0 -1
- package/companion.js +0 -389
- package/crossbrowser.d.ts.map +0 -1
- package/crossbrowser.js +0 -1191
- package/dashdone.d.ts.map +0 -1
- package/dashdone.js +0 -368
- package/deno.d.ts.map +0 -1
- package/deno.js +0 -52325
- package/devthink.umd.js +0 -52346
- package/fixtures/bun.ts +0 -7
- package/fixtures/cjs.js +0 -6
- package/fixtures/deno.ts +0 -6
- package/fixtures/example-org-pagestate.json +0 -22
- package/fixtures/importers/automa-workflow.json +0 -18
- package/fixtures/importers/selenium-side.json +0 -20
- package/fixtures/importers/tabular-plan.csv +0 -7
- package/fixtures/importers/uivision-macro.json +0 -10
- package/fixtures/importers/v1-plan.json +0 -13
- package/fixtures/mcp-client.mjs +0 -14
- package/fixtures/pages/checkout.html +0 -192
- package/fixtures/pages/dashboard.html +0 -110
- package/fixtures/pages/feed.html +0 -113
- package/fixtures/pages/product-grid.html +0 -150
- package/fixtures/plans/form-inventory-plan.json +0 -11
- package/fixtures/plans/release-notes-plan.json +0 -11
- package/fixtures/recipes/agentscompete.json +0 -11
- package/fixtures/recipes/agentsforms.json +0 -11
- package/fixtures/recipes/agentsmonitor.json +0 -11
- package/fixtures/recipes/agentsreview.json +0 -29
- package/fixtures/recipes/agentswarm.json +0 -11
- package/fixtures/recipes/fillcalendar.json +0 -12
- package/fixtures/recipes/fillcheckout.json +0 -13
- package/fixtures/recipes/filldependentdropdowns.json +0 -12
- package/fixtures/recipes/fillfromcsv.json +0 -13
- package/fixtures/recipes/fillloginform.json +0 -12
- package/fixtures/recipes/fillmultistepwizard.json +0 -13
- package/fixtures/recipes/fillregistration.json +0 -12
- package/fixtures/recipes/fillsearchfilters.json +0 -12
- package/fixtures/recipes/filltypeahead.json +0 -11
- package/fixtures/recipes/monitoralerts.json +0 -12
- package/fixtures/recipes/monitoravailability.json +0 -11
- package/fixtures/recipes/monitornetworkquiet.json +0 -12
- package/fixtures/recipes/monitorpagechanges.json +0 -11
- package/fixtures/recipes/monitorpricechange.json +0 -11
- package/fixtures/recipes/monitorschedule.json +0 -12
- package/fixtures/recipes/scrapedynamicprices.json +0 -13
- package/fixtures/recipes/scrapeimagegallery.json +0 -12
- package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
- package/fixtures/recipes/scrapenestedlists.json +0 -12
- package/fixtures/recipes/scrapeproductgrid.json +0 -12
- package/fixtures/recipes/scrapereviews.json +0 -12
- package/fixtures/recipes/scrapesearchresults.json +0 -12
- package/fixtures/recipes/scrapetablepagination.json +0 -12
- package/fixtures/recipes/testaccessibility.json +0 -34
- package/fixtures/recipes/testconsoleerrors.json +0 -12
- package/fixtures/recipes/testdeeplinks.json +0 -12
- package/fixtures/recipes/testformvalidation.json +0 -13
- package/fixtures/recipes/testlayoutshift.json +0 -12
- package/fixtures/recipes/testlinkcheck.json +0 -12
- package/fixtures/recipes/testloadtiming.json +0 -12
- package/fixtures/recipes/testvisualdiff.json +0 -12
- package/fixtures/workflows/changelog-digest-workflow.json +0 -13
- package/fixtures/workflows/form-survey-workflow.json +0 -13
- package/gallery.json +0 -706
- package/hardening.d.ts.map +0 -1
- package/hardening.js +0 -188
- package/headless.d.ts.map +0 -1
- package/headless.js +0 -11522
- package/http.d.ts.map +0 -1
- package/http.js +0 -2165
- package/index.cjs +0 -54682
- package/index.d.ts.map +0 -1
- package/index.js +0 -52310
- package/index.neutral.js +0 -46343
- package/mcp.d.ts.map +0 -1
- package/mcp.js +0 -20515
- package/memory.d.ts.map +0 -1
- package/nativehost.template.json +0 -10
- package/node.cjs +0 -54686
- package/node.d.ts.map +0 -1
- package/pack.d.ts.map +0 -1
- package/pack.js +0 -805
- package/policy.d.ts.map +0 -1
- package/progress.d.ts.map +0 -1
- package/progress.js +0 -645
- package/protocol.d.ts.map +0 -1
- package/protocol.js +0 -8334
- package/schemas/audit.schema.json +0 -226
- package/schemas/capability.schema.json +0 -113
- package/schemas/envelope.schema.json +0 -162
- package/schemas/memory.schema.json +0 -51
- package/schemas/observation.schema.json +0 -113
- package/schemas/plan.schema.json +0 -208
- package/schemas/progress.schema.json +0 -126
- package/schemas/proposal.schema.json +0 -68
- package/schemas/session.schema.json +0 -82
- package/schemas/tool.schema.json +0 -94
- package/server.d.ts.map +0 -1
- package/server.js +0 -28877
- package/umd-example.html +0 -18
- package/umd.d.ts.map +0 -1
- /package/{bun.d.ts → dist/bun.d.ts} +0 -0
- /package/{cli.d.ts → dist/cli.d.ts} +0 -0
- /package/{companion.d.ts → dist/companion.d.ts} +0 -0
- /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
- /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
- /package/{deno.d.ts → dist/deno.d.ts} +0 -0
- /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
- /package/{headless.d.ts → dist/headless.d.ts} +0 -0
- /package/{http.d.ts → dist/http.d.ts} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
- /package/{memory.d.ts → dist/memory.d.ts} +0 -0
- /package/{node.d.ts → dist/node.d.ts} +0 -0
- /package/{pack.d.ts → dist/pack.d.ts} +0 -0
- /package/{policy.d.ts → dist/policy.d.ts} +0 -0
- /package/{progress.d.ts → dist/progress.d.ts} +0 -0
- /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
- /package/{server.d.ts → dist/server.d.ts} +0 -0
- /package/{umd.d.ts → dist/umd.d.ts} +0 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
coldstartverdict,
|
|
4
|
+
durationsampleof,
|
|
5
|
+
durationsampletoperf,
|
|
6
|
+
selectorprofileof,
|
|
7
|
+
selectorstatsupdate,
|
|
8
|
+
slomopause,
|
|
9
|
+
slomoresume,
|
|
10
|
+
slomosessionof,
|
|
11
|
+
slomostepdelay,
|
|
12
|
+
spanchildren,
|
|
13
|
+
startupsampleof,
|
|
14
|
+
steptracefile,
|
|
15
|
+
steptracespanof,
|
|
16
|
+
} from "../perf.js";
|
|
17
|
+
import { slowmofactorvalid } from "../policy.js";
|
|
18
|
+
import { sessionmemory } from "../memory.js";
|
|
19
|
+
|
|
20
|
+
const now = 1_800_000_000_000;
|
|
21
|
+
|
|
22
|
+
class fakeadapter {
|
|
23
|
+
private readonly data = new Map<string, unknown>();
|
|
24
|
+
async get<T>(key: string): Promise<T | undefined> {
|
|
25
|
+
return this.data.get(key) as T | undefined;
|
|
26
|
+
}
|
|
27
|
+
async set<T>(key: string, value: T): Promise<void> {
|
|
28
|
+
this.data.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("durationmeter", () => {
|
|
33
|
+
it("measures every step with monotonic clocks and writes the samples into the perf records", () => {
|
|
34
|
+
const sample = durationsampleof({ stepid: "s1", start: 100.5, end: 220.75 });
|
|
35
|
+
expect(sample).toEqual({ stepid: "s1", start: 100.5, end: 220.75, duration: 120.25, monotonic: true });
|
|
36
|
+
expect(durationsampleof({ stepid: "s1", start: 300, end: 100 }).duration).toBe(0);
|
|
37
|
+
expect(durationsampleof({ stepid: "s1", start: 1, end: 2, monotonic: false }).monotonic).toBe(false);
|
|
38
|
+
expect(() => durationsampleof({ stepid: " ", start: 0, end: 1 })).toThrow(/step id/i);
|
|
39
|
+
const record = durationsampletoperf({
|
|
40
|
+
runid: "run1",
|
|
41
|
+
sample,
|
|
42
|
+
queries: 2,
|
|
43
|
+
cachehits: 1,
|
|
44
|
+
delta: false,
|
|
45
|
+
now,
|
|
46
|
+
provenance: { origin: "https://example.com", environment: "pagecontext", task: "durationmeter" },
|
|
47
|
+
});
|
|
48
|
+
expect(record.duration).toBe(120.25);
|
|
49
|
+
expect(record.provenance.task).toBe("durationmeter");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("selectorprofile", () => {
|
|
54
|
+
it("counts the resolution time and the failure rate per selector and flags the selectors above the user latency threshold", () => {
|
|
55
|
+
const stats = selectorprofileof({
|
|
56
|
+
selector: "#button",
|
|
57
|
+
samples: [
|
|
58
|
+
{ duration: 80, ok: true },
|
|
59
|
+
{ duration: 120, ok: true },
|
|
60
|
+
{ duration: 100, ok: false },
|
|
61
|
+
],
|
|
62
|
+
threshold: 90,
|
|
63
|
+
});
|
|
64
|
+
expect(stats).toEqual({
|
|
65
|
+
selector: "#button",
|
|
66
|
+
count: 3,
|
|
67
|
+
totalduration: 300,
|
|
68
|
+
average: 100,
|
|
69
|
+
failures: 1,
|
|
70
|
+
failurerate: 1 / 3,
|
|
71
|
+
flagged: true,
|
|
72
|
+
});
|
|
73
|
+
expect(
|
|
74
|
+
selectorprofileof({ selector: "#button", samples: [{ duration: 50, ok: true }], threshold: 90 }).flagged,
|
|
75
|
+
).toBe(false);
|
|
76
|
+
expect(selectorprofileof({ selector: "#button", samples: [] }).count).toBe(0);
|
|
77
|
+
expect(() => selectorprofileof({ selector: " ", samples: [] })).toThrow(/selector/i);
|
|
78
|
+
const updated = selectorstatsupdate(stats, { selector: "#button", duration: 100, ok: true, threshold: 90 });
|
|
79
|
+
expect(updated.count).toBe(4);
|
|
80
|
+
expect(updated.average).toBe(100);
|
|
81
|
+
expect(selectorstatsupdate(undefined, { selector: "#fresh", duration: 40, ok: true }).count).toBe(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("stores the selectorprofile stats of the profile workspace", async () => {
|
|
85
|
+
const store = new sessionmemory(new fakeadapter());
|
|
86
|
+
await store.setselectorprofile(
|
|
87
|
+
selectorprofileof({ selector: "#button", samples: [{ duration: 100, ok: true }], threshold: 90 }),
|
|
88
|
+
);
|
|
89
|
+
expect(await store.getselectorprofiles()).toHaveLength(1);
|
|
90
|
+
await store.setselectorprofile(
|
|
91
|
+
selectorprofileof({ selector: "#button", samples: [{ duration: 300, ok: false }], threshold: 90 }),
|
|
92
|
+
);
|
|
93
|
+
const latest = (await store.getselectorprofiles()).find((stats) => stats.selector === "#button");
|
|
94
|
+
expect(latest?.count).toBe(1);
|
|
95
|
+
expect(latest?.failures).toBe(1);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("steptrace", () => {
|
|
100
|
+
it("nests spans per step and per worker task through their parent refs", () => {
|
|
101
|
+
const parent = steptracespanof({ runid: "run1", stepid: "s1", start: 1, end: 5, cause: "navigate dispatch" });
|
|
102
|
+
const child = steptracespanof({
|
|
103
|
+
runid: "run1",
|
|
104
|
+
task: "htmlsnapshot",
|
|
105
|
+
start: 2,
|
|
106
|
+
end: 4,
|
|
107
|
+
cause: "worker parse",
|
|
108
|
+
parent: parent.id,
|
|
109
|
+
});
|
|
110
|
+
expect(spanchildren([parent, child], parent.id)).toEqual([child]);
|
|
111
|
+
expect(spanchildren([parent, child], child.id)).toEqual([]);
|
|
112
|
+
expect(() => steptracespanof({ runid: " ", start: 1, end: 2, cause: "x" })).toThrow(/run id/i);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("exports a trace file for the timeline view with its spans in start order", () => {
|
|
116
|
+
const late = steptracespanof({ runid: "run1", stepid: "s2", start: 10, end: 20, cause: "click dispatch" });
|
|
117
|
+
const early = steptracespanof({ runid: "run1", stepid: "s1", start: 1, end: 9, cause: "navigate dispatch" });
|
|
118
|
+
const file = steptracefile({ runid: "run1", spans: [late, early] });
|
|
119
|
+
expect(file.format).toBe("devthink-steptrace");
|
|
120
|
+
expect(file.events).toBe(2);
|
|
121
|
+
expect(file.spans.map((span) => span.stepid)).toEqual(["s1", "s2"]);
|
|
122
|
+
expect(() => steptracefile({ runid: " ", spans: [] })).toThrow(/run id/i);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("stores the steptrace spans of one run through the memory store", async () => {
|
|
126
|
+
const store = new sessionmemory(new fakeadapter());
|
|
127
|
+
await store.addsteptracespan(
|
|
128
|
+
steptracespanof({ runid: "run1", stepid: "s1", start: 1, end: 2, cause: "navigate dispatch" }),
|
|
129
|
+
);
|
|
130
|
+
await store.addsteptracespan(
|
|
131
|
+
steptracespanof({ runid: "run1", stepid: "s2", start: 3, end: 4, cause: "click dispatch" }),
|
|
132
|
+
);
|
|
133
|
+
expect(await store.getsteptrace("run1")).toHaveLength(2);
|
|
134
|
+
await store.clearsteptrace("run1");
|
|
135
|
+
expect(await store.getsteptrace("run1")).toHaveLength(0);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("startupmeter and coldstart", () => {
|
|
140
|
+
it("measures the cold start from the startup event to ready with the lazymods budget spent", () => {
|
|
141
|
+
const sample = startupsampleof({ startedat: now, readyat: now + 400, spent: 3, target: 500 });
|
|
142
|
+
expect(sample).toEqual({ startedat: now, readyat: now + 400, duration: 400, spent: 3, target: 500 });
|
|
143
|
+
expect(startupsampleof({ startedat: now, readyat: now, spent: 0 }).target).toBeUndefined();
|
|
144
|
+
const verdict = coldstartverdict({ sample, heavy: ["capture", "compare"], firstpaint: ["capture"] });
|
|
145
|
+
expect(verdict.withintarget).toBe(true);
|
|
146
|
+
expect(verdict.heavyinfirstpaint).toEqual(["capture"]);
|
|
147
|
+
const late = coldstartverdict({
|
|
148
|
+
sample: startupsampleof({ startedat: now, readyat: now + 900, spent: 2, target: 500 }),
|
|
149
|
+
heavy: [],
|
|
150
|
+
firstpaint: [],
|
|
151
|
+
});
|
|
152
|
+
expect(late.withintarget).toBe(false);
|
|
153
|
+
const untargeted = coldstartverdict({
|
|
154
|
+
sample: startupsampleof({ startedat: now, readyat: now + 900, spent: 2 }),
|
|
155
|
+
heavy: [],
|
|
156
|
+
firstpaint: [],
|
|
157
|
+
});
|
|
158
|
+
expect(untargeted.withintarget).toBe(true);
|
|
159
|
+
expect(untargeted.reason).toMatch(/no cold start target/i);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("records the startupmeter samples of the profile workspace", async () => {
|
|
163
|
+
const store = new sessionmemory(new fakeadapter());
|
|
164
|
+
await store.addstartupsample(startupsampleof({ startedat: now, readyat: now + 400, spent: 3 }));
|
|
165
|
+
await store.addstartupsample(startupsampleof({ startedat: now, readyat: now + 300, spent: 2 }));
|
|
166
|
+
const samples = await store.getstartupsamples();
|
|
167
|
+
expect(samples).toHaveLength(2);
|
|
168
|
+
expect(samples[0]?.duration).toBe(300);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("slowmo replay", () => {
|
|
173
|
+
it("replays a recorded run at the chosen speed factor with pauses between steps for inspection", () => {
|
|
174
|
+
const session = slomosessionof({ runid: "run1", factor: 0.5, now });
|
|
175
|
+
expect(session.factor).toBe(0.5);
|
|
176
|
+
expect(slomosessionof({ runid: "run1", now }).factor).toBe(1);
|
|
177
|
+
expect(() => slomosessionof({ runid: " ", now })).toThrow(/run id/i);
|
|
178
|
+
expect(slomostepdelay({ factor: 0.5, duration: 1000 })).toBe(2000);
|
|
179
|
+
expect(slomostepdelay({ factor: 2, duration: 1000 })).toBe(500);
|
|
180
|
+
const paused = slomopause({ session, stepid: "s2", spanid: "span9" });
|
|
181
|
+
expect(paused.paused).toBe(true);
|
|
182
|
+
expect(paused.stepid).toBe("s2");
|
|
183
|
+
expect(paused.spanid).toBe("span9");
|
|
184
|
+
expect(slomoresume(paused).paused).toBe(false);
|
|
185
|
+
expect(slowmofactorvalid({ factor: 0.5 }).allowed).toBe(true);
|
|
186
|
+
expect(slowmofactorvalid({}).reason).toMatch(/recorded speed/i);
|
|
187
|
+
expect(slowmofactorvalid({ factor: 0 }).allowed).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("stores the slowmo replay sessions of the profile workspace", async () => {
|
|
191
|
+
const store = new sessionmemory(new fakeadapter());
|
|
192
|
+
await store.setslomosession(slomosessionof({ runid: "run1", factor: 0.5, now }));
|
|
193
|
+
await store.setslomosession(
|
|
194
|
+
slomopause({ session: slomosessionof({ runid: "run1", factor: 0.5, now }), stepid: "s1", spanid: "span1" }),
|
|
195
|
+
);
|
|
196
|
+
const sessions = await store.getslomosessions();
|
|
197
|
+
expect(sessions).toHaveLength(1);
|
|
198
|
+
expect(sessions[0]?.paused).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
chunkextractprogress,
|
|
4
|
+
chunkextracttaskof,
|
|
5
|
+
chunkextractwindow,
|
|
6
|
+
mergechunkwindows,
|
|
7
|
+
openchunkextractcursor,
|
|
8
|
+
resumechunkextract,
|
|
9
|
+
streamparsechunkof,
|
|
10
|
+
streamparseobservations,
|
|
11
|
+
streamparsewindows,
|
|
12
|
+
validatesetreamparsechunk,
|
|
13
|
+
} from "../data.js";
|
|
14
|
+
import { chunkextractgate, streamparsegate } from "../policy.js";
|
|
15
|
+
|
|
16
|
+
describe("streamparse progressive observations", () => {
|
|
17
|
+
it("splits a large page into chunk windows without ever holding the full text", () => {
|
|
18
|
+
const windows = streamparsewindows({ bytes: 2500, chunkbytes: 1000 });
|
|
19
|
+
expect(windows.chunks).toBe(3);
|
|
20
|
+
expect(windows.windows).toEqual([
|
|
21
|
+
{ index: 0, start: 0, end: 1000 },
|
|
22
|
+
{ index: 1, start: 1000, end: 2000 },
|
|
23
|
+
{ index: 2, start: 2000, end: 2500 },
|
|
24
|
+
]);
|
|
25
|
+
expect(() => streamparsewindows({ bytes: 10, chunkbytes: 0 })).toThrow(/positive number of bytes/i);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("tokenizes one chunk into text, element and attribute tokens and refuses chunks over the byte bound", () => {
|
|
29
|
+
const chunk = streamparsechunkof({
|
|
30
|
+
index: 0,
|
|
31
|
+
text: '<h1 class="title">Hello</h1> world',
|
|
32
|
+
chunkbytes: 100,
|
|
33
|
+
last: true,
|
|
34
|
+
});
|
|
35
|
+
expect(chunk.index).toBe(0);
|
|
36
|
+
expect(chunk.complete).toBe(true);
|
|
37
|
+
expect(chunk.tokens.map((token) => token.kind)).toContain("element");
|
|
38
|
+
expect(chunk.tokens.map((token) => token.kind)).toContain("attribute");
|
|
39
|
+
expect(chunk.observations.join(" ")).toMatch(/Hello/i);
|
|
40
|
+
expect(() => streamparsechunkof({ index: 0, text: "a".repeat(50), chunkbytes: 10, last: true })).toThrow(
|
|
41
|
+
/byte bound/i,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("validates every chunk through the same schemastrict shape before any observation yields", () => {
|
|
46
|
+
const chunk = streamparsechunkof({ index: 1, text: "plain text", chunkbytes: 100, last: false });
|
|
47
|
+
expect(validatesetreamparsechunk(chunk, 1).valid).toBe(true);
|
|
48
|
+
expect(validatesetreamparsechunk(chunk, 0).valid).toBe(false);
|
|
49
|
+
expect(validatesetreamparsechunk({}, 0).valid).toBe(false);
|
|
50
|
+
expect(validatesetreamparsechunk({ index: 0, tokens: [], bytes: 0, complete: true }, 0).valid).toBe(true);
|
|
51
|
+
expect(streamparsegate({ chunk, expectedindex: 1 }).allowed).toBe(true);
|
|
52
|
+
expect(streamparsegate({ chunk, expectedindex: 2 }).allowed).toBe(false);
|
|
53
|
+
expect(streamparsegate({ chunk, expectedindex: 2 }).reason).toMatch(/out of order/i);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("yields observations progressively as chunks complete", () => {
|
|
57
|
+
const chunks = [
|
|
58
|
+
streamparsechunkof({ index: 0, text: "first part", chunkbytes: 100, last: false }),
|
|
59
|
+
streamparsechunkof({ index: 1, text: "second part", chunkbytes: 100, last: true }),
|
|
60
|
+
];
|
|
61
|
+
const yield1 = streamparseobservations([chunks[0] as never]);
|
|
62
|
+
const yield2 = streamparseobservations(chunks);
|
|
63
|
+
expect(yield1.progressive).toHaveLength(1);
|
|
64
|
+
expect(yield2.total).toBeGreaterThan(yield1.total);
|
|
65
|
+
expect(yield2.progressive[0]).toEqual(yield1.progressive[0]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("chunkextract windowing, resume and merge", () => {
|
|
70
|
+
const rows = Array.from({ length: 25 }, (_, index) => ({ id: String(index), name: `row${index}` }));
|
|
71
|
+
|
|
72
|
+
it("opens a cursor with its table fingerprint and slices the table into row windows", () => {
|
|
73
|
+
const cursor = openchunkextractcursor({
|
|
74
|
+
tableid: "table1",
|
|
75
|
+
fingerprint: "abc12345",
|
|
76
|
+
rows: rows.length,
|
|
77
|
+
window: 10,
|
|
78
|
+
});
|
|
79
|
+
expect(cursor.rowindex).toBe(0);
|
|
80
|
+
expect(cursor.window).toBe(10);
|
|
81
|
+
expect(cursor.complete).toBe(false);
|
|
82
|
+
const first = chunkextractwindow({ cursor, rows });
|
|
83
|
+
expect(first.rows).toHaveLength(10);
|
|
84
|
+
expect(first.cursor.rowindex).toBe(10);
|
|
85
|
+
expect(first.complete).toBe(false);
|
|
86
|
+
const second = chunkextractwindow({ cursor: first.cursor, rows });
|
|
87
|
+
expect(second.rows).toHaveLength(10);
|
|
88
|
+
const third = chunkextractwindow({ cursor: second.cursor, rows });
|
|
89
|
+
expect(third.rows).toHaveLength(5);
|
|
90
|
+
expect(third.cursor.complete).toBe(true);
|
|
91
|
+
expect(third.complete).toBe(true);
|
|
92
|
+
expect(() => openchunkextractcursor({ tableid: " ", fingerprint: "f", rows: 5 })).toThrow(/table id/i);
|
|
93
|
+
expect(() => openchunkextractcursor({ tableid: "t", fingerprint: " ", rows: 5 })).toThrow(/fingerprint/i);
|
|
94
|
+
expect(() => openchunkextractcursor({ tableid: "t", fingerprint: "f", rows: 5, window: 0 })).toThrow(
|
|
95
|
+
/positive row count/i,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("resumes from its cursor after an interruption and refuses changed fingerprints", () => {
|
|
100
|
+
const cursor = openchunkextractcursor({ tableid: "table1", fingerprint: "abc12345", rows: 25, window: 10 });
|
|
101
|
+
const advanced = chunkextractwindow({ cursor, rows }).cursor;
|
|
102
|
+
expect(resumechunkextract({ cursor: advanced, tableid: "table1", fingerprint: "abc12345" }).resumed).toBe(true);
|
|
103
|
+
const wrongtable = resumechunkextract({ cursor: advanced, tableid: "table2", fingerprint: "abc12345" });
|
|
104
|
+
expect(wrongtable.resumed).toBe(false);
|
|
105
|
+
expect(wrongtable.reason).toMatch(/belongs to the table table1/i);
|
|
106
|
+
const changed = resumechunkextract({ cursor: advanced, tableid: "table1", fingerprint: "changed" });
|
|
107
|
+
expect(changed.resumed).toBe(false);
|
|
108
|
+
expect(changed.reason).toMatch(/fingerprint changed/i);
|
|
109
|
+
const completed = resumechunkextract({
|
|
110
|
+
cursor: { ...advanced, complete: true },
|
|
111
|
+
tableid: "table1",
|
|
112
|
+
fingerprint: "abc12345",
|
|
113
|
+
});
|
|
114
|
+
expect(completed.resumed).toBe(false);
|
|
115
|
+
expect(chunkextractgate({ cursor: advanced, tableid: "table1", fingerprint: "abc12345" }).allowed).toBe(true);
|
|
116
|
+
expect(chunkextractgate({ cursor: advanced, tableid: "table1", fingerprint: "nope" }).allowed).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("merges the windows into the datagrid incrementally without duplicating rows", () => {
|
|
120
|
+
const cursor = openchunkextractcursor({
|
|
121
|
+
tableid: "table1",
|
|
122
|
+
fingerprint: "abc12345",
|
|
123
|
+
rows: rows.length,
|
|
124
|
+
window: 10,
|
|
125
|
+
});
|
|
126
|
+
const first = chunkextractwindow({ cursor, rows });
|
|
127
|
+
const second = chunkextractwindow({ cursor: first.cursor, rows });
|
|
128
|
+
const third = chunkextractwindow({ cursor: second.cursor, rows });
|
|
129
|
+
const merged = mergechunkwindows([first, second, third]);
|
|
130
|
+
expect(merged.rows).toHaveLength(25);
|
|
131
|
+
expect(merged.duplicates).toBe(0);
|
|
132
|
+
const repeated = mergechunkwindows([first, first]);
|
|
133
|
+
expect(repeated.duplicates).toBe(10);
|
|
134
|
+
expect(repeated.rows).toHaveLength(10);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("reads the cursor progress line and shapes the worker task payloads", () => {
|
|
138
|
+
const cursor = openchunkextractcursor({ tableid: "table1", fingerprint: "abc12345", rows: 25, window: 10 });
|
|
139
|
+
const advanced = chunkextractwindow({ cursor, rows }).cursor;
|
|
140
|
+
const progress = chunkextractprogress(advanced, 25);
|
|
141
|
+
expect(progress).toEqual({ extracted: 10, total: 25, share: 0.4, complete: false });
|
|
142
|
+
const task = chunkextracttaskof({
|
|
143
|
+
runid: "run1",
|
|
144
|
+
stepid: "s1",
|
|
145
|
+
tableid: "table1",
|
|
146
|
+
fingerprint: "abc12345",
|
|
147
|
+
rowindex: 10,
|
|
148
|
+
window: 10,
|
|
149
|
+
});
|
|
150
|
+
expect(task).toEqual({
|
|
151
|
+
task: "chunkextract",
|
|
152
|
+
runid: "run1",
|
|
153
|
+
stepid: "s1",
|
|
154
|
+
tableid: "table1",
|
|
155
|
+
fingerprint: "abc12345",
|
|
156
|
+
rowindex: 10,
|
|
157
|
+
window: 10,
|
|
158
|
+
});
|
|
159
|
+
expect(() =>
|
|
160
|
+
chunkextracttaskof({ runid: "run1", stepid: "s1", tableid: "t", fingerprint: "f", rowindex: 0, window: 0 }),
|
|
161
|
+
).toThrow(/positive row window/i);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
/** The surface sources whose generated markup the pruning scan reads beside the one design file. */
|
|
5
|
+
const surfacesources = [
|
|
6
|
+
"views.ts",
|
|
7
|
+
"web/sidepanel.ts",
|
|
8
|
+
"web/dashboardpage.ts",
|
|
9
|
+
"web/popup.ts",
|
|
10
|
+
"web/optionspage.ts",
|
|
11
|
+
"web/transparencypage.ts",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The style pruning enforcement of the 2.0.2 final polish (roadmap rc.2 item 43): the scan re-runs over web/index.html and the surface sources exactly the way the pruning pass did — every class selector of the embedded extension stylesheet must resolve to a class some surface renders through a class attribute, a className assignment (literal, ternary or template), an object literal property, a classList call or a setAttribute write.
|
|
16
|
+
* The diffrow kind suffixes stay the one documented dynamic composition: the snapshot and console diffs render `diffrow ${entry.kind}` and `diffrow ${line.kind}` (sidepanel.ts) whose kind unions of types.ts carry added, removed and changed, so those three classes stay kept and noted rather than pruned.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Collects every class token the given text renders: class attributes, className expressions with their ternaries and template literals, object literal className properties, classList calls and setAttribute writes. */
|
|
20
|
+
async function usedclassesof(text: string): Promise<{ used: Set<string>; composed: string[] }> {
|
|
21
|
+
const used = new Set<string>();
|
|
22
|
+
const composed: string[] = [];
|
|
23
|
+
for (const match of text.matchAll(/class="([^"]*)"/g))
|
|
24
|
+
for (const token of (match[1] ?? "").split(/\s+/)) if (token) used.add(token);
|
|
25
|
+
for (const match of text.matchAll(/className\s*=\s*([^;\n]+);?/g)) {
|
|
26
|
+
for (const literal of (match[1] ?? "").matchAll(/"([^"]*)"|`([^`]*)`/g)) {
|
|
27
|
+
const value = literal[1] ?? literal[2] ?? "";
|
|
28
|
+
for (const token of value.split(/[\s${}]+/)) if (token) used.add(token);
|
|
29
|
+
if (value.includes("${")) composed.push(value.trim());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const match of text.matchAll(/className:\s*"([^"]*)"/g))
|
|
33
|
+
for (const token of (match[1] ?? "").split(/\s+/)) if (token) used.add(token);
|
|
34
|
+
for (const match of text.matchAll(/classList\.(?:add|toggle|remove)\(([^;\n]+)\)/g)) {
|
|
35
|
+
for (const literal of (match[1] ?? "").matchAll(/"([^"]*)"|`([^`]*)`/g)) {
|
|
36
|
+
const value = literal[1] ?? literal[2] ?? "";
|
|
37
|
+
for (const token of value.split(/[\s${}]+/)) if (token) used.add(token);
|
|
38
|
+
if (value.includes("${")) composed.push(value.trim());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const match of text.matchAll(/setAttribute\("class",\s*"([^"]*)"\)/g))
|
|
42
|
+
for (const token of (match[1] ?? "").split(/\s+/)) if (token) used.add(token);
|
|
43
|
+
return { used, composed };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("the style pruning of the extension pages", () => {
|
|
47
|
+
it("keeps zero unused class selectors in the embedded extension stylesheet", async () => {
|
|
48
|
+
const design = await readFile("web/design.html", "utf8");
|
|
49
|
+
const stylesheet = /<style data-source="extension">([\s\S]*?)<\/style>/.exec(design)?.[1] ?? "";
|
|
50
|
+
expect(stylesheet).not.toBe("");
|
|
51
|
+
const styleclasses = new Set([...stylesheet.matchAll(/\.[a-z][a-z0-9-]*/g)].map((match) => match[0]!.slice(1)));
|
|
52
|
+
expect(styleclasses.size).toBeGreaterThan(50);
|
|
53
|
+
const { used, composed } = await usedclassesof(design);
|
|
54
|
+
for (const source of surfacesources) {
|
|
55
|
+
const scanned = await usedclassesof(await readFile(source, "utf8"));
|
|
56
|
+
for (const token of scanned.used) used.add(token);
|
|
57
|
+
composed.push(...scanned.composed);
|
|
58
|
+
}
|
|
59
|
+
/* the diffrow kind suffixes ride the kind unions the diff and console renders interpolate (sidepanel.ts `diffrow ${entry.kind}` and `diffrow ${line.kind}`), so the scan keeps them instead of pruning them: the classes the composition may produce stay documented here. */
|
|
60
|
+
const dynamiccomposed = new Set(["added", "removed", "changed"]);
|
|
61
|
+
const unused = [...styleclasses].filter((token) => !used.has(token) && !dynamiccomposed.has(token)).sort();
|
|
62
|
+
expect(unused).toEqual([]);
|
|
63
|
+
/* the noted dynamic compositions really ride a template literal over the diffrow rules the stylesheet keeps */
|
|
64
|
+
expect(composed.filter((value) => value.startsWith("diffrow ")).length).toBeGreaterThanOrEqual(2);
|
|
65
|
+
for (const token of dynamiccomposed) expect([...styleclasses].some((cls) => cls === token)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("keeps the pruned selector families out of the stylesheet while the classes the surfaces render stay declared", async () => {
|
|
69
|
+
const design = await readFile("web/design.html", "utf8");
|
|
70
|
+
const stylesheet = /<style data-source="extension">([\s\S]*?)<\/style>/.exec(design)?.[1] ?? "";
|
|
71
|
+
/* the families the 2.0.2 pruning removed: no surface template and no generated markup literal ever rendered them — the run lifecycle rows render plain list items beside runbadge, the memory marks render nothing since the statedepth pass writes plain text, the vision word boxes never render beside the wordoverlay caption, the editor library list carries no class of its own and the idem key never rendered — so the rules stay pruned and the assertion holds the pruning in place */
|
|
72
|
+
for (const pruned of [".runrow", ".idemkey", ".memorymark", "span.wordbox", ".editorlibrary"])
|
|
73
|
+
expect(stylesheet.includes(pruned)).toBe(false);
|
|
74
|
+
/* every class the surface templates render still resolves to a rule: the pruning removed rules only, never a class any surface uses */
|
|
75
|
+
const styleclasses = new Set([...stylesheet.matchAll(/\.[a-z][a-z0-9-]*/g)].map((match) => match[0]!.slice(1)));
|
|
76
|
+
for (const template of design.matchAll(/<template data-surface="([^"]+)">([\s\S]*?)<\/template>/g)) {
|
|
77
|
+
for (const match of (template[2] ?? "").matchAll(/class="([^"]*)"/g)) {
|
|
78
|
+
for (const token of (match[1] ?? "").split(/\s+/)) {
|
|
79
|
+
if (token) expect(styleclasses.has(token)).toBe(true);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/* the classes the state machines toggle stay declared exactly like the wcag sweep demands */
|
|
84
|
+
for (const source of surfacesources) {
|
|
85
|
+
const text = await readFile(source, "utf8");
|
|
86
|
+
for (const match of text.matchAll(/classList\.(?:add|toggle|remove)\("([^"]+)"\)/g))
|
|
87
|
+
expect(styleclasses.has(match[1]!)).toBe(true);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|