diff --git a/src/browser/pages/vscode.ts b/src/browser/pages/vscode.ts index 128df2c45..61648dd33 100644 --- a/src/browser/pages/vscode.ts +++ b/src/browser/pages/vscode.ts @@ -24,13 +24,13 @@ type NlsConfiguration = { * Helper function to create the path to the bundle * for getNlsConfiguration. */ -export function createBundlePath(_resolvedLanguagePackCoreLocation: string, bundle: string) { +export function createBundlePath(_resolvedLanguagePackCoreLocation: string | undefined, bundle: string) { // NOTE@jsjoeio - this comment was here before me // Refers to operating systems that use a different path separator. // Probably just Windows but we're not sure if "/" breaks on Windows // so we'll leave it alone for now. // FIXME: Only works if path separators are /. - return _resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json" + return (_resolvedLanguagePackCoreLocation || "") + "/" + bundle.replace(/\//g, "!") + ".nls.json" } /** @@ -72,20 +72,22 @@ export function getNlsConfiguration(_document: Document, base: string) { type LoadBundleCallback = (_: undefined, result?: string) => void - nlsConfig.loadBundle = (bundle: string, _language: string, cb: LoadBundleCallback): void => { + nlsConfig.loadBundle = async (bundle: string, _language: string, cb: LoadBundleCallback): Promise => { const result = bundles[bundle] + if (result) { return cb(undefined, result) } - // FIXME: Only works if path separators are /. - const path = createBundlePath(nlsConfig._resolvedLanguagePackCoreLocation || "", bundle) - fetch(`${base}/vscode/resource/?path=${encodeURIComponent(path)}`) - .then((response) => response.json()) - .then((json) => { - bundles[bundle] = json - cb(undefined, json) - }) - .catch(cb) + + try { + const path = createBundlePath(nlsConfig._resolvedLanguagePackCoreLocation, bundle) + const response = await fetch(`${base}/vscode/resource/?path=${encodeURIComponent(path)}`) + const json = await response.json() + bundles[bundle] = json + return cb(undefined, json) + } catch (error) { + return cb(error) + } } } diff --git a/test/package.json b/test/package.json index a23c4d11c..4f87bc676 100644 --- a/test/package.json +++ b/test/package.json @@ -7,14 +7,15 @@ "@types/jsdom": "^16.2.6", "@types/node-fetch": "^2.5.8", "@types/supertest": "^2.0.10", + "@types/wtfnode": "^0.7.0", "argon2": "^0.28.0", "jest": "^26.6.3", + "jest-fetch-mock": "^3.0.3", "jsdom": "^16.4.0", "node-fetch": "^2.6.1", "playwright": "^1.12.1", "supertest": "^6.1.1", "ts-jest": "^26.4.4", - "@types/wtfnode": "^0.7.0", "wtfnode": "^0.9.0" }, "resolutions": { diff --git a/test/unit/browser/pages/vscode.test.ts b/test/unit/browser/pages/vscode.test.ts index 99c109bbf..2c0d1a384 100644 --- a/test/unit/browser/pages/vscode.test.ts +++ b/test/unit/browser/pages/vscode.test.ts @@ -1,6 +1,7 @@ /** * @jest-environment jsdom */ +import fetchMock from "jest-fetch-mock" import { JSDOM } from "jsdom" import { getNlsConfiguration, @@ -20,6 +21,11 @@ describe("vscode", () => { // We use underscores to not confuse with global values const { window: _window } = new JSDOM() _document = _window.document + fetchMock.enableMocks() + }) + + afterEach(() => { + fetchMock.resetMocks() }) it("should throw an error if no nlsConfigElement", () => { @@ -60,7 +66,7 @@ describe("vscode", () => { _document.body.removeChild(mockElement) }) - it("should return have loadBundle property if _resolvedLangaugePackCoreLocation", () => { + it("should return and have a loadBundle property if _resolvedLangaugePackCoreLocation", async () => { const mockElement = _document.createElement("div") const dataSettings = { locale: "en", @@ -76,6 +82,32 @@ describe("vscode", () => { expect(nlsConfig._resolvedLanguagePackCoreLocation).not.toBe(undefined) expect(nlsConfig.loadBundle).not.toBe(undefined) + const mockCallbackFn = jest.fn((_, bundle) => { + return bundle + }) + + fetchMock.mockOnce(JSON.stringify({ key: "hello world" })) + // Ensure that load bundle works as expected + // by mocking the fetch response and checking that the callback + // had the expected value + await nlsConfig.loadBundle("hello", "en", mockCallbackFn) + expect(mockCallbackFn).toHaveBeenCalledTimes(1) + expect(mockCallbackFn).toHaveBeenCalledWith(undefined, { key: "hello world" }) + + // Call it again to ensure it loads from the cache + // it should return the same value + await nlsConfig.loadBundle("hello", "en", mockCallbackFn) + expect(mockCallbackFn).toHaveBeenCalledTimes(2) + expect(mockCallbackFn).toHaveBeenCalledWith(undefined, { key: "hello world" }) + + fetchMock.mockReject(new Error("fake error message")) + const mockCallbackFn2 = jest.fn((error) => error) + // Call it for a different bundle and mock a failed fetch call + // to ensure we get the expected error + const error = await nlsConfig.loadBundle("goodbye", "es", mockCallbackFn2) + expect(error.message).toEqual("fake error message") + + // Clean up _document.body.removeChild(mockElement) }) }) @@ -87,6 +119,13 @@ describe("vscode", () => { const actual = createBundlePath(_resolvedLangaugePackCoreLocation, bundle) expect(actual).toBe(expected) }) + it("should return the correct path (even if _resolvedLangaugePackCoreLocation is undefined)", () => { + const _resolvedLangaugePackCoreLocation = undefined + const bundle = "/bundle.js" + const expected = "/!bundle.js.nls.json" + const actual = createBundlePath(_resolvedLangaugePackCoreLocation, bundle) + expect(actual).toBe(expected) + }) }) describe("setBodyBackgroundToThemeBackgroundColor", () => { let _document: Document @@ -228,11 +267,6 @@ describe("vscode", () => { }, recordStats: true, - // TODO@jsjoeio address trustedTypesPolicy part - // might need to look up types - // and find a way to test the function - // maybe extract function into function - // and test manually trustedTypesPolicy: undefined, "vs/nls": { availableLanguages: {}, @@ -280,6 +314,11 @@ describe("vscode", () => { expect(loader.trustedTypesPolicy).not.toBe(undefined) expect(loader.trustedTypesPolicy.name).toBe("amdLoader") + + // Check that we can actually create a script URL + // using the createScriptURL on the loader object + const scriptUrl = loader.trustedTypesPolicy.createScriptURL("http://localhost/foo.js") + expect(scriptUrl).toBe("http://localhost/foo.js") }) }) describe("_createScriptURL", () => { diff --git a/test/yarn.lock b/test/yarn.lock index 418eeeecc..2fa48122c 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -1757,6 +1757,13 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cross-fetch@^3.0.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== + dependencies: + node-fetch "2.6.1" + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -2800,6 +2807,14 @@ jest-environment-node@^26.6.2: jest-mock "^26.6.2" jest-util "^26.6.2" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" @@ -3418,7 +3433,7 @@ node-addon-api@^3.0.2: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-fetch@^2.6.1: +node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -3762,6 +3777,11 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-polyfill@^8.1.3: + version "8.2.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== + prompts@^2.0.1: version "2.4.0" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7"