diff --git a/.prettierignore b/.prettierignore index 54c6fab53a1..1ec50cc8e92 100644 --- a/.prettierignore +++ b/.prettierignore @@ -38,6 +38,7 @@ yarn.lock # Third party files /wwwroot/third_party +/wwwroot/mockServiceWorker.js # Ignore some files doc/acknowledgements/attributions.md diff --git a/buildprocess/ci-deploy.sh b/buildprocess/ci-deploy.sh index 3cce06ce937..72aa7325182 100644 --- a/buildprocess/ci-deploy.sh +++ b/buildprocess/ci-deploy.sh @@ -22,7 +22,7 @@ npm install -g yarn@^1.22.22 # Clone and build TerriaMap, using this version of TerriaJS TERRIAJS_COMMIT_HASH=$(git rev-parse HEAD) -git clone -b main https://github.com/TerriaJS/TerriaMap.git +git clone -b msw-poc https://github.com/TerriaJS/TerriaMap.git cd TerriaMap TERRIAMAP_COMMIT_HASH=$(git rev-parse HEAD) sed -i -e 's@"terriajs": ".*"@"terriajs": "'$GITHUB_REPOSITORY'#'${GITHUB_BRANCH}'"@g' package.json diff --git a/buildprocess/createKarmaBaseConfig.js b/buildprocess/createKarmaBaseConfig.js index 3ccf1ab5d69..7501f61cfac 100644 --- a/buildprocess/createKarmaBaseConfig.js +++ b/buildprocess/createKarmaBaseConfig.js @@ -30,7 +30,8 @@ module.exports = function (config) { "/data": "/base/data", "/images": "/base/images", "/test": "/base/test", - "/build": "/base/build" + "/build": "/base/build", + "/mockServiceWorker.js": "/base/mockServiceWorker.js" }, // list of files to exclude diff --git a/package.json b/package.json index 769ec25e7c0..e405dcf223f 100644 --- a/package.json +++ b/package.json @@ -194,6 +194,7 @@ "karma-safari-launcher": "^1.0.0", "karma-spec-reporter": "^0.0.36", "minimist": "^1.2.8", + "msw": "^2.12.9", "node-notifier": "^10.0.1", "plugin-error": "^2.0.1", "prettier": "2.8.8", @@ -218,5 +219,10 @@ "prettier-check": "prettier --check .", "build-for-node": "tsc -b tsconfig-node.json", "prepare": "yarn build-for-node && husky install" + }, + "msw": { + "workerDirectory": [ + "wwwroot" + ] } } diff --git a/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionJobSpec.ts b/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionJobSpec.ts index 9ad116e5959..71915305ffa 100644 --- a/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionJobSpec.ts +++ b/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionJobSpec.ts @@ -1,8 +1,10 @@ import { configure, reaction } from "mobx"; +import { http, HttpResponse } from "msw"; import YDYRCatalogFunctionJob from "../../../../lib/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionJob"; import CsvCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/CsvCatalogItem"; import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; import Terria from "../../../../lib/Models/Terria"; +import { worker } from "../../../mocks/browser"; import "../../../SpecHelpers"; // For more tests see - test\Models\YDYRCatalogFunctionSpec.ts @@ -21,43 +23,43 @@ describe("YDYRCatalogFunctionJob", function () { let job: YDYRCatalogFunctionJob; beforeEach(function () { - jasmine.Ajax.install(); - - jasmine.Ajax.stubRequest( - "http://example.com/api/v1/download/someResultKey?format=csv" - ).andReturn({ - responseText: `SA4_code_2016,Negative Binomial: Lower (10%),Negative Binomial: Upper (90%),Negative Binomial: Average + let logCounter = 0; + worker.use( + http.get( + "http://example.com/api/v1/download/someResultKey", + ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get("format") !== "csv") + throw new Error(`Unexpected query params: ${url.search}`); + + return new HttpResponse(`SA4_code_2016,Negative Binomial: Lower (10%),Negative Binomial: Upper (90%),Negative Binomial: Average 313,0,1,0 316,0,1,0 -` - }); - - let logCounter = 0; - jasmine.Ajax.stubRequest( - "http://example.com/api/v1/status/someStatusId" - ).andCallFunction((req) => { - if (logCounter < 1) { - req.respondWith({ responseText: `"Some Log ${logCounter}"` }); - - logCounter++; - } else { - req.respondWith({ - responseText: `{"key":"someResultKey","report":{"Quality Control":"OK (Model is performing better than baseline), providing full result"}}` - }); - } - }); - - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json" - ).andReturn({ responseJSON: sa4regionCodes }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-FID_LGA_2011_AUST_LGA_CODE11.json" - ).andReturn({ responseJSON: lga2011RegionCodes }); +`); + } + ), + http.get("http://example.com/api/v1/status/someStatusId", () => { + if (logCounter < 1) { + const msg = `"Some Log ${logCounter}"`; + logCounter++; + return new HttpResponse(msg); + } + return new HttpResponse( + `{"key":"someResultKey","report":{"Quality Control":"OK (Model is performing better than baseline), providing full result"}}` + ); + }), + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json", + () => HttpResponse.json(sa4regionCodes) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-FID_LGA_2011_AUST_LGA_CODE11.json", + () => HttpResponse.json(lga2011RegionCodes) + ) + ); terria = new Terria(); @@ -75,10 +77,6 @@ describe("YDYRCatalogFunctionJob", function () { job.setTrait(CommonStrata.definition, "jobId", "someStatusId"); }); - afterEach(function () { - jasmine.Ajax.uninstall(); - }); - it("has a type & typeName", function () { expect(YDYRCatalogFunctionJob.type).toBe("ydyr-job"); expect(job.typeName).toBe("YourDataYourRegions Job"); diff --git a/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionSpec.ts b/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionSpec.ts index b24c2ba739f..074835927bf 100644 --- a/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionSpec.ts +++ b/test/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionSpec.ts @@ -1,10 +1,12 @@ import { configure, reaction, toJS } from "mobx"; +import { http, HttpResponse } from "msw"; import addUserCatalogMember from "../../../../lib/Models/Catalog/addUserCatalogMember"; import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; import CsvCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/CsvCatalogItem"; import Terria from "../../../../lib/Models/Terria"; import YDYRCatalogFunction from "../../../../lib/Models/Catalog/CatalogFunctions/YDYRCatalogFunction"; import YDYRCatalogFunctionJob from "../../../../lib/Models/Catalog/CatalogFunctions/YDYRCatalogFunctionJob"; +import { worker } from "../../../mocks/browser"; import "../../../SpecHelpers"; import regionMapping from "../../../../wwwroot/data/regionMapping.json"; @@ -24,46 +26,46 @@ describe("YDYRCatalogFunction", function () { let ydyr: YDYRCatalogFunction; beforeEach(async function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "http://example.com/api/v1/disaggregate.json" - ).andReturn({ responseText: `"someStatusId"` }); - - jasmine.Ajax.stubRequest( - "http://example.com/api/v1/download/someResultKey?format=csv" - ).andReturn({ - responseText: `SA4_code_2016,Negative Binomial: Lower (10%),Negative Binomial: Upper (90%),Negative Binomial: Average + let logCounter = 0; + worker.use( + http.all( + "http://example.com/api/v1/disaggregate.json", + () => new HttpResponse(`"someStatusId"`) + ), + http.get( + "http://example.com/api/v1/download/someResultKey", + ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get("format") !== "csv") + throw new Error(`Unexpected query params: ${url.search}`); + return new HttpResponse(`SA4_code_2016,Negative Binomial: Lower (10%),Negative Binomial: Upper (90%),Negative Binomial: Average 313,0,1,0 316,0,1,0 -` - }); - - let logCounter = 0; - jasmine.Ajax.stubRequest( - "http://example.com/api/v1/status/someStatusId" - ).andCallFunction((req) => { - if (logCounter < 1) { - req.respondWith({ responseText: `"Some Log ${logCounter}"` }); - - logCounter++; - } else { - req.respondWith({ - responseText: `{"key":"someResultKey","report":{"Quality Control":"OK (Model is performing better than baseline), providing full result"}}` - }); - } - }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json" - ).andReturn({ responseJSON: sa4regionCodes }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-FID_LGA_2011_AUST_LGA_CODE11.json" - ).andReturn({ responseJSON: lga2011RegionCodes }); - - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); +`); + } + ), + http.get("http://example.com/api/v1/status/someStatusId", () => { + if (logCounter < 1) { + const msg = `"Some Log ${logCounter}"`; + logCounter++; + return new HttpResponse(msg); + } + return new HttpResponse( + `{"key":"someResultKey","report":{"Quality Control":"OK (Model is performing better than baseline), providing full result"}}` + ); + }), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json", + () => HttpResponse.json(sa4regionCodes) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-FID_LGA_2011_AUST_LGA_CODE11.json", + () => HttpResponse.json(lga2011RegionCodes) + ), + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ) + ); terria = new Terria(); csv = new CsvCatalogItem("test", terria, undefined); @@ -94,10 +96,6 @@ describe("YDYRCatalogFunction", function () { }); }); - afterEach(function () { - jasmine.Ajax.uninstall(); - }); - it("has a type & typeName", function () { expect(YDYRCatalogFunction.type).toBe("ydyr"); expect(ydyr.typeName).toBe("YourDataYourRegions"); diff --git a/test/Models/Catalog/CatalogGroups/SocrataCatalogGroupSpec.ts b/test/Models/Catalog/CatalogGroups/SocrataCatalogGroupSpec.ts index d34b9e7855d..dd98d160589 100644 --- a/test/Models/Catalog/CatalogGroups/SocrataCatalogGroupSpec.ts +++ b/test/Models/Catalog/CatalogGroups/SocrataCatalogGroupSpec.ts @@ -1,8 +1,10 @@ import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; import Terria from "../../../../lib/Models/Terria"; import SocrataCatalogGroup from "../../../../lib/Models/Catalog/CatalogGroups/SocrataCatalogGroup"; import CatalogGroup from "../../../../lib/Models/Catalog/CatalogGroup"; import SocrataMapViewCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/SocrataMapViewCatalogItem"; +import { worker } from "../../../mocks/browser"; import facets from "../../../../wwwroot/test/Socrata/facets.json"; import search from "../../../../wwwroot/test/Socrata/search.json"; @@ -12,16 +14,27 @@ describe("SocrataCatalogGroup", function () { let socrataGroup: SocrataCatalogGroup; beforeEach(function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "http://example.com/api/catalog/v1/domains/example.com/facets?only=dataset%2Cmap" - ).andReturn({ - responseJSON: facets - }); - - jasmine.Ajax.stubRequest( - "http://example.com/api/catalog/v1?search_context=example.com&only=dataset%2Cmap&categories=Environment" - ).andReturn({ responseJSON: search }); + worker.use( + http.get( + "http://example.com/api/catalog/v1/domains/example.com/facets", + ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get("only") !== "dataset,map") + throw new Error(`Unexpected query params: ${url.search}`); + return HttpResponse.json(facets); + } + ), + http.get("http://example.com/api/catalog/v1", ({ request }) => { + const url = new URL(request.url); + if ( + url.searchParams.get("search_context") !== "example.com" || + url.searchParams.get("only") !== "dataset,map" || + url.searchParams.get("categories") !== "Environment" + ) + throw new Error(`Unexpected query params: ${url.search}`); + return HttpResponse.json(search); + }) + ); terria = new Terria(); socrataGroup = new SocrataCatalogGroup("test", terria); @@ -31,10 +44,6 @@ describe("SocrataCatalogGroup", function () { }); }); - afterEach(function () { - jasmine.Ajax.uninstall(); - }); - it("has a type", function () { expect(socrataGroup.type).toBe("socrata-group"); }); diff --git a/test/Models/Catalog/CatalogItems/CesiumTerrainCatalogItemSpec.ts b/test/Models/Catalog/CatalogItems/CesiumTerrainCatalogItemSpec.ts index 6593af18ced..ca6d5907841 100644 --- a/test/Models/Catalog/CatalogItems/CesiumTerrainCatalogItemSpec.ts +++ b/test/Models/Catalog/CatalogItems/CesiumTerrainCatalogItemSpec.ts @@ -3,6 +3,8 @@ import CesiumTerrainCatalogItem from "../../../../lib/Models/Catalog/CatalogItem import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; import CesiumTerrainProvider from "terriajs-cesium/Source/Core/CesiumTerrainProvider"; import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; +import { worker } from "../../../mocks/browser"; describe("CesiumTerrainCatalogItem", function () { let terria: Terria; @@ -19,18 +21,13 @@ describe("CesiumTerrainCatalogItem", function () { beforeEach(function () { terria = new Terria(); item = new CesiumTerrainCatalogItem(undefined, terria); - jasmine.Ajax.install(); - }); - - afterEach(function () { - jasmine.Ajax.uninstall(); }); describe("loading", function () { it("rejects with an error when there is a network error", async function () { - jasmine.Ajax.stubRequest(/no-such-server/).andReturn({ - status: undefined - }); + worker.use( + http.get("http://no-such-server/*", () => HttpResponse.error()) + ); item.setTrait(CommonStrata.user, "url", "http://no-such-server"); const result = await item.loadMapItems(); expect(result.error?.message).toBeDefined( @@ -39,10 +36,11 @@ describe("CesiumTerrainCatalogItem", function () { }); it("can load terrain from a URL", async function () { - jasmine.Ajax.stubRequest(/foo/).andReturn({ - status: 200, - responseJSON: validResponse - }); + worker.use( + http.get("https://example.com/foo/*", () => + HttpResponse.json(validResponse) + ) + ); item.setTrait(CommonStrata.user, "url", "https://example.com/foo/bar"); const result = await item.loadMapItems(); @@ -53,20 +51,19 @@ describe("CesiumTerrainCatalogItem", function () { }); it("can load terrain from `ionAssetId`", async function () { - // Stub request from IonResource - jasmine.Ajax.stubRequest(/424242/).andReturn({ - status: 200, - responseJSON: { - url: "foo/bar", - attributions: [] - } - }); - - // Stub request for terrain - jasmine.Ajax.stubRequest(/foo/).andReturn({ - status: 200, - responseJSON: validResponse - }); + worker.use( + // Stub request from IonResource + http.get("https://api.cesium.com/v1/assets/424242/endpoint", () => + HttpResponse.json({ + url: "https://example.com/foo/bar", + attributions: [] + }) + ), + // Stub request for terrain + http.get("https://example.com/foo/*", () => + HttpResponse.json(validResponse) + ) + ); item.setTrait(CommonStrata.user, "ionAssetId", 424242); const result = await item.loadMapItems(); @@ -79,10 +76,11 @@ describe("CesiumTerrainCatalogItem", function () { describe("mapItems", function () { it("should be empty when `show` is false", async function () { - jasmine.Ajax.stubRequest(/foo/).andReturn({ - status: 200, - responseJSON: validResponse - }); + worker.use( + http.get("https://example.com/foo/*", () => + HttpResponse.json(validResponse) + ) + ); item.setTrait(CommonStrata.user, "url", "https://example.com/foo/bar"); await item.loadMapItems(); runInAction(() => { diff --git a/test/Models/Catalog/CatalogItems/IonImageryCatalogItemSpec.ts b/test/Models/Catalog/CatalogItems/IonImageryCatalogItemSpec.ts index 6b275c13e73..c4f1479ae97 100644 --- a/test/Models/Catalog/CatalogItems/IonImageryCatalogItemSpec.ts +++ b/test/Models/Catalog/CatalogItems/IonImageryCatalogItemSpec.ts @@ -1,8 +1,10 @@ import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; import IonImageryProvider from "terriajs-cesium/Source/Scene/IonImageryProvider"; import { ImageryParts } from "../../../../lib/ModelMixins/MappableMixin"; import IonImageryCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/IonImageryCatalogItem"; import Terria from "../../../../lib/Models/Terria"; +import { worker } from "../../../mocks/browser"; describe("IonImageryCatalogItem", function () { const item = new IonImageryCatalogItem("test", new Terria()); @@ -13,17 +15,6 @@ describe("IonImageryCatalogItem", function () { describe("the mapItem", function () { beforeEach(async function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "https://example.com/v1/assets/12345/endpoint?access_token=fakeAccessToken" - ).andReturn({ - responseText: JSON.stringify({ - type: "IMAGERY", - url: "https://example.com", - attributions: [] - }) - }); - const validSampleXmlString = '' + " NE2_HR_LC_SR_W_DR_recolored.tif" + @@ -40,12 +31,29 @@ describe("IonImageryCatalogItem", function () { " " + ""; - jasmine.Ajax.stubRequest( - "https://example.com/tilemapresource.xml" - ).andReturn({ - responseText: validSampleXmlString, - contentType: "text/xml" - }); + worker.use( + http.get( + "https://example.com/v1/assets/12345/endpoint", + ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get("access_token") !== "fakeAccessToken") + throw new Error(`Unexpected query params: ${url.search}`); + + return HttpResponse.json({ + type: "IMAGERY", + url: "https://example.com", + attributions: [] + }); + } + ), + http.get( + "https://example.com/tilemapresource.xml", + () => + new HttpResponse(validSampleXmlString, { + headers: { "Content-Type": "text/xml" } + }) + ) + ); runInAction(() => { item.setTrait("definition", "ionAssetId", 12345); @@ -55,10 +63,6 @@ describe("IonImageryCatalogItem", function () { await item.loadMapItems(); }); - afterEach(function () { - jasmine.Ajax.uninstall(); - }); - it("correctly sets the `alpha` value", function () { if (!ImageryParts.is(item.mapItems[0])) throw new Error("Expected MapItem to be an ImageryParts"); diff --git a/test/Models/Catalog/CatalogItems/OpenDataSoftCatalogItemSpec.ts b/test/Models/Catalog/CatalogItems/OpenDataSoftCatalogItemSpec.ts index 96444a950ab..00b272e1bf2 100644 --- a/test/Models/Catalog/CatalogItems/OpenDataSoftCatalogItemSpec.ts +++ b/test/Models/Catalog/CatalogItems/OpenDataSoftCatalogItemSpec.ts @@ -1,8 +1,9 @@ import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; import OpenDataSoftCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/OpenDataSoftCatalogItem"; import Terria from "../../../../lib/Models/Terria"; -import fetchMock from "fetch-mock"; import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; +import { worker } from "../../../mocks/browser"; import dataset from "../../../../wwwroot/test/ods/weather-station-dataset.json"; import groupBy from "../../../../wwwroot/test/ods/weather-station-groupby.json"; @@ -14,36 +15,31 @@ describe("OpenDataSoftCatalogItem", function () { let odsItem: OpenDataSoftCatalogItem; beforeEach(function () { - fetchMock.mock( - "https://example.com/api/v2/catalog/datasets/weather-stations/", - { body: JSON.stringify(dataset) } + worker.use( + http.get( + "https://example.com/api/v2/catalog/datasets/weather-stations/", + () => HttpResponse.json(dataset) + ), + http.get( + "https://example.com/api/v2/catalog/datasets/weather-stations/records/", + ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.has("group_by")) + return HttpResponse.json(groupBy); + if (url.searchParams.has("order_by")) + return HttpResponse.json(weatherStationData); + throw new Error(`Unexpected query params: ${url.search}`); + } + ), + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ) ); - fetchMock.mock( - "https://example.com/api/v2/catalog/datasets/weather-stations/records/?group_by=geolocation&limit=100&select=min%28metadata_time%29+as+min_time%2C+max%28metadata_time%29+as+max_time%2C+count%28metadata_time%29+as+num", - { body: JSON.stringify(groupBy) } - ); - - fetchMock.mock( - "https://example.com/api/v2/catalog/datasets/weather-stations/records/?limit=100&offset=0&order_by=metadata_time+DESC&select=device_name%2C+dev_id%2C+metadata_time%2C+heat_stress_index%2C+payload_fields_uvindex%2C+payload_fields_wshumidity%2C+payload_fields_wstemperature%2C+payload_fields_airpressure%2C+payload_fields_brightness%2C+payload_fields_gustdirection%2C+payload_fields_gustspeed%2C+payload_fields_precip%2C+payload_fields_precipint%2C+payload_fields_radiation%2C+payload_fields_winddirection%2C+payload_fields_windspeed%2C+payload_fields_averagespl%2C+payload_fields_carbonmonoxide%2C+payload_fields_humidity%2C+payload_fields_ibatt%2C+payload_fields_nitrogendioxide%2C+payload_fields_ozone%2C+payload_fields_particulateserr%2C+payload_fields_particulatesvsn%2C+payload_fields_peakspl%2C+payload_fields_pm1%2C+payload_fields_pm10%2C+payload_fields_pm25%2C+payload_fields_temperature%2C+payload_fields_vbatt%2C+payload_fields_vpanel%2C+payload_fields_fixstatus%2C+payload_fields_hdop%2C+payload_fields_nsat%2C+geolocation%2C+organisation", - { body: JSON.stringify(weatherStationData) } - ); - - jasmine.Ajax.install(); - - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); - terria = new Terria(); odsItem = new OpenDataSoftCatalogItem("test", terria, undefined); }); - afterEach(function () { - jasmine.Ajax.uninstall(); - fetchMock.restore(); - }); - it("has a type", function () { expect(odsItem.type).toBe("opendatasoft-item"); }); diff --git a/test/Models/Catalog/CatalogItems/SocrataMapViewCatalogItemSpec.ts b/test/Models/Catalog/CatalogItems/SocrataMapViewCatalogItemSpec.ts index f83bdb33f2f..84d628c528e 100644 --- a/test/Models/Catalog/CatalogItems/SocrataMapViewCatalogItemSpec.ts +++ b/test/Models/Catalog/CatalogItems/SocrataMapViewCatalogItemSpec.ts @@ -1,6 +1,8 @@ import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; import SocrataMapViewCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/SocrataMapViewCatalogItem"; import Terria from "../../../../lib/Models/Terria"; +import { worker } from "../../../mocks/browser"; import view from "../../../../wwwroot/test/Socrata/view.json"; @@ -9,10 +11,11 @@ describe("SocrataMapViewCatalogItem", function () { let socrataItem: SocrataMapViewCatalogItem; beforeEach(function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest("http://example.com/views/y79a-us3f").andReturn({ - responseJSON: view - }); + worker.use( + http.get("http://example.com/views/y79a-us3f", () => + HttpResponse.json(view) + ) + ); terria = new Terria(); socrataItem = new SocrataMapViewCatalogItem("test", terria); @@ -23,10 +26,6 @@ describe("SocrataMapViewCatalogItem", function () { }); }); - afterEach(function () { - jasmine.Ajax.uninstall(); - }); - it("has a type", function () { expect(socrataItem.type).toBe("socrata-map-item"); }); diff --git a/test/Models/Catalog/CatalogReferences/CatalogIndexReferenceSpec.ts b/test/Models/Catalog/CatalogReferences/CatalogIndexReferenceSpec.ts index 54c5dc20d12..e558f300a1a 100644 --- a/test/Models/Catalog/CatalogReferences/CatalogIndexReferenceSpec.ts +++ b/test/Models/Catalog/CatalogReferences/CatalogIndexReferenceSpec.ts @@ -1,8 +1,10 @@ +import { http, HttpResponse } from "msw"; import CatalogIndexReference from "../../../../lib/Models/Catalog/CatalogReferences/CatalogIndexReference"; import MagdaReference from "../../../../lib/Models/Catalog/CatalogReferences/MagdaReference"; import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; import updateModelFromJson from "../../../../lib/Models/Definition/updateModelFromJson"; import Terria from "../../../../lib/Models/Terria"; +import { worker } from "../../../mocks/browser"; describe("CatalogIndexReference", function () { let terria: Terria; @@ -13,14 +15,6 @@ describe("CatalogIndexReference", function () { describe("loadReference", function () { describe("when a parent container is a deeply nested reference", function () { - beforeEach(function () { - jasmine.Ajax.install(); - }); - - afterEach(function () { - jasmine.Ajax.uninstall(); - }); - it("recursively loads all the references to fully expand the references", async function () { // We are setting up the following hierarchy: MagdaReference -> TerriaReference -> Group -> Leaf // and asserting that we load the reference containers all the way down to the Leaf node @@ -34,7 +28,7 @@ describe("CatalogIndexReference", function () { definition: { name: "Group", description: "Group", - url: "example.org/catalog.json" + url: "https://example.org/catalog.json" }, id: "some-group", type: "terria-reference" @@ -45,17 +39,19 @@ describe("CatalogIndexReference", function () { }); // The terria-reference points to a group containg a 'leaf' node. - jasmine.Ajax.stubRequest("example.org/catalog.json").andReturn({ - responseJSON: { - catalog: [ - { - type: "group", - name: "Group", - members: [{ id: "leaf", type: "csv", name: "leaf" }] - } - ] - } - }); + worker.use( + http.get("https://example.org/catalog.json", () => + HttpResponse.json({ + catalog: [ + { + type: "group", + name: "Group", + members: [{ id: "leaf", type: "csv", name: "leaf" }] + } + ] + }) + ) + ); const leafIndex = new CatalogIndexReference("leaf", terria); terria.addModel(parent); diff --git a/test/Models/Catalog/Ckan/CkanItemReferenceSpec.ts b/test/Models/Catalog/Ckan/CkanItemReferenceSpec.ts index 787db946e1f..53eb5e541b0 100644 --- a/test/Models/Catalog/Ckan/CkanItemReferenceSpec.ts +++ b/test/Models/Catalog/Ckan/CkanItemReferenceSpec.ts @@ -1,9 +1,11 @@ import i18next from "i18next"; import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; import CkanItemReference from "../../../../lib/Models/Catalog/Ckan/CkanItemReference"; import WebMapServiceCatalogItem from "../../../../lib/Models/Catalog/Ows/WebMapServiceCatalogItem"; import Terria from "../../../../lib/Models/Terria"; import WebMapServiceCatalogGroup from "../../../../lib/Models/Catalog/Ows/WebMapServiceCatalogGroup"; +import { worker } from "../../../mocks/browser"; import taxationStatisticsPackage from "../../../../wwwroot/test/CKAN/taxation-statistics-package.json"; import taxationStatisticsWmsResource from "../../../../wwwroot/test/CKAN/taxation-statistics-wms-resource.json"; @@ -21,38 +23,34 @@ describe("CkanItemReference", function () { }); ckanItemReference = new CkanItemReference("test", terria); - jasmine.Ajax.install(); - // Fail and log requests by default. - jasmine.Ajax.stubRequest(/.*/).andCallFunction((request) => { - console.dir(request); - request.respondWith({ status: 404 }); - }); - - jasmine.Ajax.stubRequest( - "https://example.com/api/3/action/package_show?id=tax-stats-package" - ).andReturn({ responseJSON: taxationStatisticsPackage }); - - jasmine.Ajax.stubRequest( - "https://example.com/api/3/action/resource_show?id=tax-stats-wms-resource" - ).andReturn({ - responseJSON: taxationStatisticsWmsResource - }); - - jasmine.Ajax.stubRequest( - "https://example.com/api/3/action/resource_show?id=wms-no-layers-resource" - ).andReturn({ - responseJSON: wmsNoLayerResource - }); - - jasmine.Ajax.stubRequest( - "https://example.com/api/3/action/resource_show?id=vic-wms-resource" - ).andReturn({ - responseJSON: vicWmsLayerResource - }); - }); - - afterEach(function () { - jasmine.Ajax.uninstall(); + worker.use( + http.get( + "https://example.com/api/3/action/package_show", + ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get("id") !== "tax-stats-package") + throw new Error(`Unexpected query params: ${url.search}`); + return HttpResponse.json(taxationStatisticsPackage); + } + ), + http.get( + "https://example.com/api/3/action/resource_show", + ({ request }) => { + const url = new URL(request.url); + const id = url.searchParams.get("id"); + if (id === "tax-stats-wms-resource") + return HttpResponse.json(taxationStatisticsWmsResource); + if (id === "wms-no-layers-resource") + return HttpResponse.json(wmsNoLayerResource); + if (id === "vic-wms-resource") + return HttpResponse.json(vicWmsLayerResource); + throw new Error(`Unexpected resource id: ${id}`); + } + ), + http.all("*", () => { + return new HttpResponse(null, { status: 404 }); + }) + ); }); it("has a type and typeName", function () { diff --git a/test/ReactViews/Custom/Chart/FeatureInfoPanelChartSpec.tsx b/test/ReactViews/Custom/Chart/FeatureInfoPanelChartSpec.tsx index 30edcb8a400..8335a999a28 100644 --- a/test/ReactViews/Custom/Chart/FeatureInfoPanelChartSpec.tsx +++ b/test/ReactViews/Custom/Chart/FeatureInfoPanelChartSpec.tsx @@ -1,8 +1,5 @@ -import { - render, - screen, - waitForElementToBeRemoved -} from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { http, HttpResponse } from "msw"; import { ThemeProvider } from "styled-components"; import CsvCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/CsvCatalogItem"; import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; @@ -16,6 +13,7 @@ import CustomComponent, { } from "../../../../lib/ReactViews/Custom/CustomComponent"; import parseCustomHtmlToReact from "../../../../lib/ReactViews/Custom/parseCustomHtmlToReact"; import { terriaTheme } from "../../../../lib/ReactViews/StandardUserInterface"; +import { worker } from "../../../mocks/browser"; import regionMapping from "../../../../wwwroot/data/regionMapping.json"; @@ -42,21 +40,12 @@ describe("FeatureInfoPanelChart", function () { CustomComponent.register(new CsvChartCustomComponent()); - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); - - // Without stubbing the chart csv, the specs could fail due to loadMapItems - // not finishing by the time the test renderer returns. - // So expect some race conditions here. - jasmine.Ajax.stubRequest("test/csv_nongeo/x_height.csv").andReturn({ - responseText: csv - }); - }); - - afterEach(function () { - jasmine.Ajax.uninstall(); + worker.use( + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ), + http.get("*/test/csv_nongeo/x_height.csv", () => new HttpResponse(csv)) + ); }); describe("yColumn prop", function () { @@ -66,11 +55,9 @@ describe("FeatureInfoPanelChart", function () { ``, context ); - await waitForElementToBeRemoved(() => - screen.queryByText("chart.noData") + await waitFor(() => + expect(screen.getByText("Plant Height x x")).toBeVisible() ); - - expect(screen.getByText("Plant Height x x")).toBeVisible(); }); it("renders nothing if the specified y-column does not exist", async function () { @@ -90,11 +77,7 @@ describe("FeatureInfoPanelChart", function () { context ); - await waitForElementToBeRemoved(() => - screen.queryByText("chart.noData") - ); - - expect(screen.getByText("Z x x")).toBeVisible(); + await waitFor(() => expect(screen.getByText("Z x x")).toBeVisible()); }); }); }); @@ -105,9 +88,9 @@ describe("FeatureInfoPanelChart", function () { context ); - await waitForElementToBeRemoved(() => screen.queryByText("chart.noData")); - - expect(screen.getAllByText("life-time")[1]).toBeVisible(); + await waitFor(() => + expect(screen.getAllByText("life-time")[1]).toBeVisible() + ); }); }); diff --git a/test/ReactViews/DimensionSelectorSectionSpec.tsx b/test/ReactViews/DimensionSelectorSectionSpec.tsx index 0c2c9962435..b3f116897b9 100644 --- a/test/ReactViews/DimensionSelectorSectionSpec.tsx +++ b/test/ReactViews/DimensionSelectorSectionSpec.tsx @@ -1,6 +1,7 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { runInAction } from "mobx"; +import { http, HttpResponse } from "msw"; import { ThemeProvider } from "styled-components"; import CatalogMemberMixin from "../../lib/ModelMixins/CatalogMemberMixin"; import CsvCatalogItem from "../../lib/Models/Catalog/CatalogItems/CsvCatalogItem"; @@ -16,6 +17,7 @@ import Terria from "../../lib/Models/Terria"; import { terriaTheme } from "../../lib/ReactViews/StandardUserInterface"; import SelectableDimensionSection from "../../lib/ReactViews/Workbench/Controls/SelectableDimensionSection"; import CatalogMemberTraits from "../../lib/Traits/TraitsClasses/CatalogMemberTraits"; +import { worker } from "../mocks/browser"; import lgaCode2015 from "../../wwwroot/test/csv/lga_code_2015.csv"; import lgaCodeJson from "../../wwwroot/data/regionids/region_map-FID_LGA_2015_AUST_LGA_CODE15.json"; @@ -154,22 +156,19 @@ describe("DimensionSelectorSection", function () { }); it("shows csv region mapping options", async function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ - responseJSON: regionMapping - }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-FID_LGA_2015_AUST_LGA_CODE15.json" - ).andReturn({ - responseJSON: lgaCodeJson - }); - - jasmine.Ajax.stubRequest("test/csv/lga_code_2015.csv").andReturn({ - responseText: lgaCode2015 - }); + worker.use( + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-FID_LGA_2015_AUST_LGA_CODE15.json", + () => HttpResponse.json(lgaCodeJson) + ), + http.get( + "*/test/csv/lga_code_2015.csv", + () => new HttpResponse(lgaCode2015) + ) + ); const csvItem = new CsvCatalogItem("some-csv", terria, undefined); @@ -203,8 +202,6 @@ describe("DimensionSelectorSection", function () { name: "models.tableData.manualRegionMapping" }) ).toBeVisible(); - - jasmine.Ajax.uninstall(); }); describe("when given a SelectableDimensionCheckboxGroup", function () { diff --git a/test/SpecMain.ts b/test/SpecMain.ts index 24cb76dc3e9..5e40bd6c4bb 100644 --- a/test/SpecMain.ts +++ b/test/SpecMain.ts @@ -7,6 +7,7 @@ import registerCatalogMembers from "../lib/Models/Catalog/registerCatalogMembers import JasmineDOM from "@testing-library/jasmine-dom"; import { initReactI18next } from "react-i18next"; import english from "../wwwroot/languages/en/translation.json"; +import { worker } from "./mocks/browser"; configure({ enforceActions: "always", @@ -29,6 +30,17 @@ spy((event) => { beforeAll(async function () { jasmine.addMatchers(JasmineDOM); + // Unregister stale service workers from previous test runs, + // then start MSW worker for network interception. + const registrations = await navigator.serviceWorker.getRegistrations(); + for (const reg of registrations) { + await reg.unregister(); + } + await worker.start({ + onUnhandledRequest: "bypass", + quiet: true + }); + await i18next.use(initReactI18next).init({ lng: "cimode", debug: false, @@ -40,6 +52,10 @@ beforeAll(async function () { }); }); +afterEach(function () { + worker.resetHandlers(); +}); + jasmine.getEnv().addReporter({ specDone: (result) => (result.failedExpectations || []).forEach((expectation) => diff --git a/test/Table/TableColumnSpec.ts b/test/Table/TableColumnSpec.ts index bfddd88ea69..7df39d21bed 100644 --- a/test/Table/TableColumnSpec.ts +++ b/test/Table/TableColumnSpec.ts @@ -1,3 +1,4 @@ +import { http, HttpResponse } from "msw"; import CommonStrata from "../../lib/Models/Definition/CommonStrata"; import createStratumInstance from "../../lib/Models/Definition/createStratumInstance"; import CsvCatalogItem from "../../lib/Models/Catalog/CatalogItems/CsvCatalogItem"; @@ -5,6 +6,7 @@ import Terria from "../../lib/Models/Terria"; import TableColumn from "../../lib/Table/TableColumn"; import TableColumnTraits from "../../lib/Traits/TraitsClasses/Table/ColumnTraits"; import TableColumnType from "../../lib/Table/TableColumnType"; +import { worker } from "../mocks/browser"; import regionMapping from "../../wwwroot/data/regionMapping.json"; @@ -349,14 +351,11 @@ describe("TableColumn", function () { describe("valuesAsDates", function () { beforeEach(function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); - }); - - afterEach(function () { - jasmine.Ajax.uninstall(); + worker.use( + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ) + ); }); it("defaults to dd/mm/yyyy dates", async function () { @@ -455,14 +454,11 @@ describe("TableColumn", function () { describe("column transformation", function () { beforeEach(function () { - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); - }); - - afterEach(function () { - jasmine.Ajax.uninstall(); + worker.use( + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ) + ); }); it("simple expression", async function () { diff --git a/test/Table/TableStyleSpec.ts b/test/Table/TableStyleSpec.ts index c53e8ac9c16..dcd13f669e3 100644 --- a/test/Table/TableStyleSpec.ts +++ b/test/Table/TableStyleSpec.ts @@ -15,6 +15,9 @@ import TableColumnTraits, { } from "../../lib/Traits/TraitsClasses/Table/ColumnTraits"; import TableStyleTraits from "../../lib/Traits/TraitsClasses/Table/StyleTraits"; +import { http, HttpResponse } from "msw"; +import { worker } from "../mocks/browser"; + import regionMapping from "../../wwwroot/data/regionMapping.json"; import SedCods from "../../wwwroot/data/regionids/region_map-SED_CODE18_SED_2018.json"; import Sa4Codes from "../../wwwroot/data/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json"; @@ -33,30 +36,24 @@ describe("TableStyle", function () { terria.configParameters.regionMappingDefinitionsUrl = "build/TerriaJS/data/regionMapping.json"; - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest(/.*/).andError({ - statusText: "Unexpected request, not stubbed" - }); - - jasmine.Ajax.stubRequest( - "build/TerriaJS/data/regionMapping.json" - ).andReturn({ responseJSON: regionMapping }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-SED_CODE18_SED_2018.json" - ).andReturn({ responseJSON: SedCods }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json" - ).andReturn({ responseJSON: Sa4Codes }); - - jasmine.Ajax.stubRequest( - "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_NAME16.json" - ).andReturn({ responseJSON: Sa4Names }); - }); - - afterEach(() => { - jasmine.Ajax.uninstall(); + worker.use( + http.get("*/build/TerriaJS/data/regionMapping.json", () => + HttpResponse.json(regionMapping) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-SED_CODE18_SED_2018.json", + () => HttpResponse.json(SedCods) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_CODE16.json", + () => HttpResponse.json(Sa4Codes) + ), + http.get( + "https://tiles.terria.io/region-mapping/regionids/region_map-SA4_2016_AUST_SA4_NAME16.json", + () => HttpResponse.json(Sa4Names) + ), + http.all("*", () => HttpResponse.error()) + ); }); describe(" - Scalar", function () { diff --git a/test/fixtures/terrain/terrain.ts b/test/fixtures/terrain/terrain.ts new file mode 100644 index 00000000000..d5809cedef0 --- /dev/null +++ b/test/fixtures/terrain/terrain.ts @@ -0,0 +1,30 @@ +// Minimal valid quantized-mesh terrain tile. +// Serves as a no-op terrain response so Cesium terrain loading succeeds +// without making real API calls. +export const TERRAIN_TILE = Uint8Array.from( + atob( + "DyjONRkhR8H/8xPXCNZJQR9p84sdA1HBpI68wezRtsEPKA41GSFHwf/z0+n61UlBH2ljoAQDUcFS" + + "2+dvmXHnQOYvRP25a96/3gsHMY794D9PNmgpVnPmvxEAAAAAAJCAtz/XQEbAuD/9/5CAtz9uf7g/" + + "AABtf4+AAAD+/wAAAAAAADpBAAAAADlB8IAAALg/AAC3P7U/xL4AAFU/Vj9VPxxZPlSzQ9UsjIGi" + + "QSfZolxtO9hXhDKmFGeAo2ZWFH6YQBMUAAAAAAAAAAAAAwABAAAAAAADAAQAAQAEAAAABAAAAAQAA" + + "AAGAAQAAgAGAAAAAQAHAAIAAgAFAAAAAQAGAAAABwAGAAAAAgAIAAEAAAAGAAQABgABAAUABQAAAA" + + "AAAwACAAcACQAHAAEAAAAEAAcABwAGAAAAAgAIAAEABQAAAAAADQAOAAYAAwADAAAAAAAFAAEABQAA" + + "AAUADwAQAAoACwADAAAADQAPAAwAASIAAAAp3CjcKtwp3CncKNwp3CrcKtwq2yjbKNsp2yncKdwp" + + "2yjbAgEAAAD/BFYAAABSAAAAeyJnZW9tZXRyaWNlcnJvciI6MzAuNzEwOTQyMjgyMTk5NjA3LC" + + "JsZWFmIjp0cnVlLCJzdXJmYWNlYXJlYSI6NDM1ODA1Mjk1Mi42MDM5OTE1fQ==" + ), + (c) => c.charCodeAt(0) +); + +// Minimal layer.json for CesiumTerrainProvider — zoom 0 only, two tiles. +export const LAYER_JSON = { + tilejson: "2.1.0", + format: "quantized-mesh-1.0", + version: "1.2.0", + scheme: "tms", + tiles: ["{z}/{x}/{y}.terrain"], + minzoom: 0, + maxzoom: 0, + bounds: [-180, -90, 180, 90], + available: [[{ startX: 0, startY: 0, endX: 1, endY: 0 }]] +}; diff --git a/test/mocks/browser.ts b/test/mocks/browser.ts new file mode 100644 index 00000000000..46f0aad3d4e --- /dev/null +++ b/test/mocks/browser.ts @@ -0,0 +1,35 @@ +import { http, HttpResponse } from "msw"; +import { setupWorker } from "msw/browser"; +import { TERRAIN_TILE, LAYER_JSON } from "../fixtures/terrain/terrain"; + +// Default handlers that persist across worker.resetHandlers() calls. +// These serve minimal valid responses for Cesium Ion terrain loading +// so tests never hit real external APIs. +export const worker = setupWorker( + // Ion asset endpoint → return a mock terrain server URL + http.get("https://api.cesium.com/v1/assets/:assetId/endpoint", ({ params }) => + HttpResponse.json({ + type: "TERRAIN", + url: `https://assets.cesium.com/${params.assetId}/`, + accessToken: "mock-token" + }) + ), + + // Catch-all for other api.cesium.com requests + http.all("https://api.cesium.com/*", () => HttpResponse.error()), + + // Serve terrain data from *.cesium.com subdomains + http.get("https://*.cesium.com/*", ({ request }) => { + const url = new URL(request.url); + if (url.pathname.endsWith("/layer.json")) { + return HttpResponse.json(LAYER_JSON); + } + if (url.pathname.endsWith(".terrain")) { + return new HttpResponse(TERRAIN_TILE.buffer, { + headers: { "Content-Type": "application/vnd.quantized-mesh" } + }); + } + // Unknown pattern — block + return HttpResponse.error(); + }) +); diff --git a/wwwroot/mockServiceWorker.js b/wwwroot/mockServiceWorker.js new file mode 100644 index 00000000000..daa58d0f120 --- /dev/null +++ b/wwwroot/mockServiceWorker.js @@ -0,0 +1,349 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.12.10' +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() + + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been terminated (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + interceptedAt: requestInterceptedAt, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/yarn.lock b/yarn.lock index 12a49125094..4237402e063 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1399,6 +1399,43 @@ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== +"@inquirer/ansi@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@inquirer/ansi/-/ansi-1.0.2.tgz#674a4c4d81ad460695cb2a1fc69d78cd187f337e" + integrity sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ== + +"@inquirer/confirm@^5.0.0": + version "5.1.21" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.21.tgz#610c4acd7797d94890a6e2dde2c98eb1e891dd12" + integrity sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ== + dependencies: + "@inquirer/core" "^10.3.2" + "@inquirer/type" "^3.0.10" + +"@inquirer/core@^10.3.2": + version "10.3.2" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.3.2.tgz#535979ff3ff4fe1e7cc4f83e2320504c743b7e20" + integrity sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A== + dependencies: + "@inquirer/ansi" "^1.0.2" + "@inquirer/figures" "^1.0.15" + "@inquirer/type" "^3.0.10" + cli-width "^4.1.0" + mute-stream "^2.0.0" + signal-exit "^4.1.0" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.3" + +"@inquirer/figures@^1.0.15": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a" + integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== + +"@inquirer/type@^3.0.10": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.10.tgz#11ed564ec78432a200ea2601a212d24af8150d50" + integrity sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA== + "@jridgewell/gen-mapping@^0.3.12": version "0.3.12" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" @@ -1511,6 +1548,18 @@ rw "^1.3.3" tinyqueue "^3.0.0" +"@mswjs/interceptors@^0.41.2": + version "0.41.3" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.41.3.tgz#d766dc1a168aa315a6a0b2d0f2e0cf1b74f23c82" + integrity sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA== + dependencies: + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/logger" "^0.3.0" + "@open-draft/until" "^2.0.0" + is-node-process "^1.2.0" + outvariant "^1.4.3" + strict-event-emitter "^0.5.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1532,6 +1581,24 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@open-draft/deferred-promise@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" + integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== + +"@open-draft/logger@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" + integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== + dependencies: + is-node-process "^1.2.0" + outvariant "^1.4.0" + +"@open-draft/until@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" + integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== + "@opendatasoft/api-client@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@opendatasoft/api-client/-/api-client-0.1.0.tgz#8aa2f065346c786981f5bedbed926afcfb6a8af4" @@ -2346,6 +2413,11 @@ "@types/geojson" "*" "@types/node" "*" +"@types/statuses@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.6.tgz#66748315cc9a96d63403baa8671b2c124f8633aa" + integrity sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA== + "@types/styled-components@^5.1.34": version "5.1.34" resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.34.tgz#4107df8ef8a7eaba4fa6b05f78f93fba4daf0300" @@ -3578,6 +3650,11 @@ classnames@2.x, classnames@^2.2.1, classnames@^2.2.5, classnames@^2.2.6, classna resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -3822,6 +3899,11 @@ cookie@^0.7.1, cookie@~0.7.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== +cookie@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + copy-props@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-4.0.0.tgz#01d249198b8c2e4d8a5e87b90c9630f52c99a9c9" @@ -5679,6 +5761,11 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql@^16.12.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.12.0.tgz#28cc2462435b1ac3fdc6976d030cef83a0c13ac7" + integrity sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -5780,6 +5867,11 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +headers-polyfill@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" + integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== + hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -6198,6 +6290,11 @@ is-negative-zero@^2.0.3: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== +is-node-process@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" + integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== + is-number-object@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" @@ -7196,6 +7293,30 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msw@^2.12.9: + version "2.12.10" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.12.10.tgz#6d3ca80f6d13715d2b65da03f9f07b46647c3e20" + integrity sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw== + dependencies: + "@inquirer/confirm" "^5.0.0" + "@mswjs/interceptors" "^0.41.2" + "@open-draft/deferred-promise" "^2.2.0" + "@types/statuses" "^2.0.6" + cookie "^1.0.2" + graphql "^16.12.0" + headers-polyfill "^4.0.2" + is-node-process "^1.2.0" + outvariant "^1.4.3" + path-to-regexp "^6.3.0" + picocolors "^1.1.1" + rettime "^0.10.1" + statuses "^2.0.2" + strict-event-emitter "^0.5.1" + tough-cookie "^6.0.0" + type-fest "^5.2.0" + until-async "^3.0.2" + yargs "^17.7.2" + mustache@^2.2.1: version "2.3.2" resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" @@ -7211,6 +7332,11 @@ mute-stdout@^2.0.0: resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-2.0.0.tgz#c6a9b4b6185d3b7f70d3ffcb734cbfc8b0f38761" integrity sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ== +mute-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" + integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== + nanoid@^3.3.7, nanoid@^3.3.8: version "3.3.8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" @@ -7451,6 +7577,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +outvariant@^1.4.0, outvariant@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873" + integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -7577,6 +7708,11 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" +path-to-regexp@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" + integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== + path-to-regexp@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" @@ -8504,6 +8640,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== +rettime@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/rettime/-/rettime-0.10.1.tgz#cc8bb9870343f282b182e5a276899c08b94914be" + integrity sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -8847,6 +8988,11 @@ signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -9001,6 +9147,11 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: optionalDependencies: bare-events "^2.2.0" +strict-event-emitter@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" + integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -9245,6 +9396,11 @@ tabbable@^6.0.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== +tagged-tag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6" + integrity sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== + tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -9405,6 +9561,18 @@ tinyqueue@^3.0.0: resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-3.0.0.tgz#101ea761ccc81f979e29200929e78f1556e3661e" integrity sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g== +tldts-core@^7.0.23: + version "7.0.23" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.0.23.tgz#47bf18282a44641304a399d247703413b5d3e309" + integrity sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ== + +tldts@^7.0.5: + version "7.0.23" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.0.23.tgz#444f0f0720fa777839a23ea665e04f61ee57217a" + integrity sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw== + dependencies: + tldts-core "^7.0.23" + tmp@^0.2.1: version "0.2.3" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" @@ -9446,6 +9614,13 @@ tough-cookie@^4.0.0: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-6.0.0.tgz#11e418b7864a2c0d874702bc8ce0f011261940e5" + integrity sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w== + dependencies: + tldts "^7.0.5" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -9522,6 +9697,13 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^5.2.0: + version "5.4.4" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.4.4.tgz#577f165b5ecb44cfc686559cc54ca77f62aa374d" + integrity sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw== + dependencies: + tagged-tag "^1.0.0" + type-is@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" @@ -9697,6 +9879,11 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +until-async@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/until-async/-/until-async-3.0.2.tgz#447f1531fdd7bb2b4c7a98869bdb1a4c2a23865f" + integrity sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw== + update-browserslist-db@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" @@ -10354,6 +10541,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yoctocolors-cjs@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + zstddec@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.1.0.tgz#7050f3f0e0c3978562d0c566b3e5a427d2bad7ec"