diff options
Diffstat (limited to 'devtools/client/application/test')
101 files changed, 9313 insertions, 0 deletions
diff --git a/devtools/client/application/test/browser/browser.toml b/devtools/client/application/test/browser/browser.toml new file mode 100644 index 0000000000..5dea736ed3 --- /dev/null +++ b/devtools/client/application/test/browser/browser.toml @@ -0,0 +1,150 @@ +[DEFAULT] +tags = "devtools" +subsuite = "devtools" +support-files = [ + "head.js", + "resources/manifest/icon.svg", + "resources/manifest/load-fail.html", + "resources/manifest/load-no-manifest.html", + "resources/manifest/load-ok-icons.html", + "resources/manifest/load-ok-json-error.html", + "resources/manifest/load-ok-manifest-link.html", + "resources/manifest/load-ok-warnings.html", + "resources/manifest/load-ok.html", + "resources/manifest/manifest.json", + "resources/service-workers/controlled-install-sw.js", + "resources/service-workers/controlled-install.html", + "resources/service-workers/debug-sw.js", + "resources/service-workers/debug.html", + "resources/service-workers/dynamic-registration.html", + "resources/service-workers/empty.html", + "resources/service-workers/empty-sw.js", + "resources/service-workers/scope-page.html", + "resources/service-workers/simple.html", + "resources/service-workers/simple-unicode.html", + "!/devtools/client/debugger/test/mochitest/shared-head.js", + "!/devtools/client/shared/test/shared-head.js", + "!/devtools/client/shared/test/telemetry-test-helpers.js", +] + +["browser_application_panel_debug-service-worker.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = [ + "debug", # Bug 1559591 + "asan", # Bug 1575578 + "!serviceworker_e10s", # Bug 1588154 +] + +["browser_application_panel_list-domain-workers.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = ["debug"] # Bug 1559591 + +["browser_application_panel_list-multiple-workers-same-registration.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = ["debug"] # Bug 1559591 + +["browser_application_panel_list-several-workers.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = ["debug"] # Bug 1559591 + +["browser_application_panel_list-single-worker.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = ["debug"] # Bug 1559591 + +["browser_application_panel_list-unicode.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = ["debug"] # Bug 1559591 + +["browser_application_panel_list-workers-empty.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_manifest-display.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_manifest-load.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_manifest-open-json.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_manifest-reload.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_open-links.js"] +skip-if = ["true"] # Bug 1467256, 1559591 + +["browser_application_panel_sidebar.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_start-service-worker.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = [ + "asan", # Bug 1559487 + "debug", # Bug 1559591 + "!serviceworker_e10s", + "tsan", # Bug 1608640 +] + +["browser_application_panel_target-switching.js"] +https_first_disabled = true +skip-if = [ + "os == 'win'", # Bug 1640234 + "os == 'linux'", # Bug 1640234 +] + +["browser_application_panel_telemetry-debug-worker.js"] +https_first_disabled = true +skip-if = [ + "asan", # Bug 1559487 + "debug", # Bug 1559591 + "!serviceworker_e10s", # Bug 1608640 + "os == 'linux' && bits == 64 && !debug", # Bug 1654354 +] + +["browser_application_panel_telemetry-select-page.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_application_panel_telemetry-start-worker.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = [ + "ccov", + "asan", # Bug 1559487 + "debug", # Bug 1559591 + "!serviceworker_e10s", # Bug 1608640 + "tsan", # Bug 1654468 +] + +["browser_application_panel_telemetry-unregister-worker.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = [ + "asan", # Bug 1559487 + "debug", # Bug 1559591 + "!serviceworker_e10s", # Bug 1608640 +] + +["browser_application_panel_unregister-worker.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = ["debug"] # Bug 1559591 + +["browser_application_panel_viewsource-service-worker.js"] +https_first_disabled = true +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = [ + "debug", # Bug 1559591 + "asan", # Bug 1575578 + "!serviceworker_e10s", # Bug 1588154 +] + +["browser_application_panel_worker-states.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled +skip-if = [ + "asan", # Bug 1559487 + "debug", # Bug 1559591 + "!serviceworker_e10s", # Bug 1608640 +] diff --git a/devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js b/devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js new file mode 100644 index 0000000000..57dd000edb --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js", + this +); + +const TAB_URL = URL_ROOT + "resources/service-workers/debug.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab, toolbox, commands } = await openNewTabAndApplicationPanel( + TAB_URL + ); + + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + const container = getWorkerContainers(doc)[0]; + info("Wait until the inspect link is displayed"); + await waitUntil(() => { + return container.querySelector(".js-inspect-link"); + }); + + info("Click on the inspect link and wait for debugger to be ready"); + const debugLink = container.querySelector(".js-inspect-link"); + debugLink.click(); + await waitFor(() => toolbox.getPanel("jsdebugger")); + + // add a breakpoint at line 11 + const debuggerContext = createDebuggerContext(toolbox); + await waitForLoadedSource(debuggerContext, "debug-sw.js"); + await addBreakpoint(debuggerContext, "debug-sw.js", 11); + + // force a pause at the breakpoint + info("Invoke fetch, expect the service worker script to pause on line 11"); + await ContentTask.spawn(tab.linkedBrowser, {}, async function () { + content.wrappedJSObject.fetchFromWorker(); + }); + await waitForPaused(debuggerContext); + const workerScript = findSource(debuggerContext, "debug-sw.js"); + assertPausedAtSourceAndLine(debuggerContext, workerScript.id, 11); + await resume(debuggerContext); + + // remove breakpoint + await removeBreakpoint(debuggerContext, workerScript.id, 11); + + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js b/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js new file mode 100644 index 0000000000..ccb0884d0e --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the application panel only displays service workers from the + * current domain. + */ + +const SIMPLE_URL = URL_ROOT + "resources/service-workers/simple.html"; +const OTHER_URL = SIMPLE_URL.replace("example.com", "test1.example.com"); +const EMPTY_URL = (URL_ROOT + "resources/service-workers/empty.html").replace( + "example.com", + "test2.example.com" +); + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, commands, tab } = await openNewTabAndApplicationPanel( + SIMPLE_URL + ); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + let scopeEl = getWorkerContainers(doc)[0].querySelector(".js-sw-scope"); + ok( + scopeEl.textContent.startsWith("example.com"), + "First service worker registration is displayed for the correct domain" + ); + + info( + "Navigate to another page for a different domain with no service worker" + ); + + await navigateTo(EMPTY_URL); + info("Wait until the service worker list is updated"); + await waitUntil( + () => doc.querySelector(".js-registration-list-empty") !== null + ); + ok( + true, + "No service workers are shown for an empty page in a different domain." + ); + + info( + "Navigate to another page for a different domain with another service worker" + ); + await navigateTo(OTHER_URL); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + scopeEl = getWorkerContainers(doc)[0].querySelector(".js-sw-scope"); + ok( + scopeEl.textContent.startsWith("test1.example.com"), + "Second service worker registration is displayed for the correct domain" + ); + + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js b/devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js new file mode 100644 index 0000000000..8e117927d1 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const WORKER1_URL = URL_ROOT + "resources/service-workers/simple.html"; +const WORKER2_URL = URL_ROOT + "resources/service-workers/debug.html"; + +add_task(async function () { + await enableApplicationPanel(); + + await openTabAndWaitForWorker(WORKER1_URL); + const { panel, tab, commands } = await openTabAndWaitForWorker(WORKER2_URL); + + const doc = panel.panelWin.document; + + let registrationContainer = getWorkerContainers(doc)[0]; + + info("Wait until the unregister button is displayed for the registration"); + await waitUntil(() => { + registrationContainer = getWorkerContainers(doc)[0]; + return registrationContainer.querySelector(".js-unregister-button"); + }); + + const scopeEl = registrationContainer.querySelector(".js-sw-scope"); + const expectedScope = + "example.com/browser/devtools/client/application/test/" + + "browser/resources/service-workers"; + ok( + scopeEl.textContent.startsWith(expectedScope), + "Registration has the expected scope" + ); + + // check the workers data + // note that the worker from WORKER2_URL will appear second in the list with + // the "installed" state + info("Check the workers data for this registration"); + const workers = registrationContainer.querySelectorAll(".js-sw-worker"); + is(workers.length, 2, "Registration has two workers"); + // check url for worker from WORKER1_URL + const url1El = workers[0].querySelector(".js-source-url"); + is(url1El.textContent, "empty-sw.js", "First worker has correct URL"); + // check url for worker from WORKER2_URL + const url2El = workers[1].querySelector(".js-source-url"); + is(url2El.textContent, "debug-sw.js", "Second worker has correct URL"); + + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +async function openTabAndWaitForWorker(url) { + const { panel, commands, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + return { panel, commands, tab }; +} diff --git a/devtools/client/application/test/browser/browser_application_panel_list-several-workers.js b/devtools/client/application/test/browser/browser_application_panel_list-several-workers.js new file mode 100644 index 0000000000..4e1295082e --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_list-several-workers.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the application panel can display several service workers applying to the + * same domain. + */ + +const SIMPLE_URL = URL_ROOT + "resources/service-workers/simple.html"; +const OTHER_SCOPE_URL = URL_ROOT + "resources/service-workers/scope-page.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, commands, tab } = await openNewTabAndApplicationPanel( + SIMPLE_URL + ); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + info("Wait until the unregister button is displayed for the service worker"); + await waitUntil(() => + getWorkerContainers(doc)[0].querySelector(".js-unregister-button") + ); + + ok(true, "First service worker registration is displayed"); + + info( + "Navigate to another page for the same domain with another service worker" + ); + await navigateTo(OTHER_SCOPE_URL); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 2); + + info("Wait until the unregister button is displayed for the service worker"); + await waitUntil(() => + getWorkerContainers(doc)[1].querySelector(".js-unregister-button") + ); + + ok(true, "Second service worker registration is displayed"); + + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_list-single-worker.js b/devtools/client/application/test/browser/browser_application_panel_list-single-worker.js new file mode 100644 index 0000000000..74cab13bf6 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_list-single-worker.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = + URL_ROOT + "resources/service-workers/dynamic-registration.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Check for non-existing service worker"); + const isWorkerListEmpty = !!doc.querySelector(".js-registration-list-empty"); + ok(isWorkerListEmpty, "No Service Worker displayed"); + + info("Register a service worker in the page."); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + content.wrappedJSObject.registerServiceWorker(); + }); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => !!getWorkerContainers(doc).length); + + let workerContainer = getWorkerContainers(doc)[0]; + + info("Wait until the unregister button is displayed for the service worker"); + await waitUntil(() => { + workerContainer = getWorkerContainers(doc)[0]; + return workerContainer.querySelector(".js-unregister-button"); + }); + + const scopeEl = workerContainer.querySelector(".js-sw-scope"); + const expectedScope = + "example.com/browser/devtools/client/application/test/" + + "browser/resources/service-workers"; + ok( + scopeEl.textContent.startsWith(expectedScope), + "Service worker has the expected scope" + ); + + const updatedEl = workerContainer.querySelector(".js-sw-updated"); + ok( + updatedEl.textContent.includes(`${new Date().getFullYear()}`), + "Service worker has a last updated time" + ); + + info("Unregister the service worker"); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + const registration = await content.wrappedJSObject.sw; + registration.unregister(); + }); + + info("Wait until the service worker is removed from the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 0); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_list-unicode.js b/devtools/client/application/test/browser/browser_application_panel_list-unicode.js new file mode 100644 index 0000000000..c747fa9ae3 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_list-unicode.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = ( + URL_ROOT + "resources/service-workers/simple-unicode.html" +).replace("example.com", "xn--hxajbheg2az3al.xn--jxalpdlp"); + +/** + * Check that the application panel displays filenames and URL's in human-readable, + * Unicode characters, and not encoded URI's or punycode. + */ + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + const workerContainer = getWorkerContainers(doc)[0]; + + const scopeEl = workerContainer.querySelector(".js-sw-scope"); + ok( + scopeEl.textContent.startsWith( + "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1." + + "\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE" + ), + "Service worker has the expected Unicode scope" + ); + const urlEl = workerContainer.querySelector(".js-source-url"); + ok( + urlEl.textContent.endsWith("\u65E5\u672C"), + "Service worker has the expected Unicode url" + ); + + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js b/devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js new file mode 100644 index 0000000000..1bb75d2999 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the application panel only displays service workers from the + * current domain. + */ + +const EMPTY_URL = URL_ROOT + "resources/service-workers/empty.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab } = await openNewTabAndApplicationPanel(EMPTY_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + await waitUntil( + () => doc.querySelector(".js-registration-list-empty") !== null + ); + ok(true, "No service workers are shown for an empty page"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-display.js b/devtools/client/application/test/browser/browser_application_panel_manifest-display.js new file mode 100644 index 0000000000..632c547f42 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_manifest-display.js @@ -0,0 +1,158 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the manifest is being properly shown + */ + +add_task(async function () { + info("Test that we are displaying correctly a valid manifest"); + const url = URL_ROOT + "resources/manifest/load-ok.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to be displayed"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest is being displayed"); + + // assert manifest members are being properly displayed + checkManifestMember(doc, "name", "Foo"); + checkManifestMember(doc, "background_color", "#ff0000"); + + Assert.strictEqual( + doc.querySelector(".js-manifest-issues"), + null, + "No validation issues are being displayed" + ); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info( + "Test that we are displaying correctly a manifest with validation warnings" + ); + const url = URL_ROOT + "resources/manifest/load-ok-warnings.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to be displayed"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest is being displayed"); + + // assert manifest members are being properly displayed + checkManifestMember(doc, "name", "Foo"); + checkManifestMember(doc, "background_color", ""); + + const issuesEl = doc.querySelector(".js-manifest-issues"); + Assert.notStrictEqual(issuesEl, null, "Validation issues are displayed"); + + const warningEl = [...issuesEl.querySelectorAll(".js-manifest-issue")].find( + x => x.textContent.includes("background_color") + ); + Assert.notStrictEqual( + warningEl, + null, + "A warning about background_color is displayed" + ); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info("Test that we are displaying correctly a manifest with JSON errors"); + const url = URL_ROOT + "resources/manifest/load-ok-json-error.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to be displayed"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest is being displayed"); + + const issuesEl = doc.querySelector(".js-manifest-issues"); + Assert.notStrictEqual(issuesEl, null, "Validation issues are displayed"); + + const errorEl = [...issuesEl.querySelectorAll(".js-manifest-issue")].find(x => + x.textContent.includes("JSON") + ); + Assert.notStrictEqual( + errorEl, + null, + "An error about JSON parsing is displayed" + ); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info("Test that we are displaying correctly a manifest with icons"); + const url = URL_ROOT + "resources/manifest/load-ok-icons.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to be displayed"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest is being displayed"); + + // assert manifest icon is being displayed + const iconEl = findMemberByLabel(doc, "128x128image/svg"); + Assert.notStrictEqual( + iconEl, + null, + "Icon label is being displayed with size and image type" + ); + const imgEl = iconEl.querySelector(".js-manifest-item-content img"); + Assert.notStrictEqual(imgEl, null, "An image is displayed for the icon"); + is( + imgEl.src, + URL_ROOT + "resources/manifest/icon.svg", + "The icon image has the the icon url as source" + ); + const iconTextContent = iconEl.querySelector( + ".js-manifest-item-content" + ).textContent; + ok(iconTextContent.includes("any"), "Purpose is being displayed"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +function findMemberByLabel(doc, member) { + return [...doc.querySelectorAll(".js-manifest-item")].find(x => + x.querySelector(".js-manifest-item-label").textContent.startsWith(member) + ); +} + +function checkManifestMember(doc, member, expectedValue) { + const itemEl = findMemberByLabel(doc, member); + is( + itemEl.querySelector(".js-manifest-item-content").textContent, + expectedValue, + `Manifest member ${member} displays the correct value` + ); +} diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-load.js b/devtools/client/application/test/browser/browser_application_panel_manifest-load.js new file mode 100644 index 0000000000..dd89fbc1a6 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_manifest-load.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the application panel fetches a manifest when in the Manifest Page + */ + +add_task(async function () { + info("Test that manifest page loads the manifest successfully"); + const url = URL_ROOT + "resources/manifest/load-ok.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to load"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest loaded successfully"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info("Test that manifest page shows an error when failing to load"); + const url = URL_ROOT + "resources/manifest/load-fail.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to fail to load"); + await waitUntil( + () => doc.querySelector(".js-manifest-loaded-error") !== null + ); + ok(true, "Manifest page displays loading error"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info("Test that manifest page shows a message when there is no manifest"); + const url = URL_ROOT + "resources/manifest/load-no-manifest.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the 'no manifest' message to appear"); + await waitUntil(() => doc.querySelector(".js-manifest-empty") !== null); + ok(true, "Manifest page displays a 'no manifest' message"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js b/devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js new file mode 100644 index 0000000000..7869502d4b --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the application panel fetches a manifest when in the Manifest Page + */ + +add_task(async function () { + info("Test that manifest page has a link that opens the manifest JSON file"); + const url = URL_ROOT_SSL + "resources/manifest/load-ok-manifest-link.html"; + const manifestUrl = URL_ROOT_SSL + "resources/manifest/manifest.json"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest JSON link"); + await waitUntil(() => doc.querySelector(".js-manifest-json-link") !== null); + ok(true, "Link to JSON is displayed"); + + info("Click on link and wait till the JSON is opened in a new tab"); + // click on the link and wait for the new tab to open + const onTabLoaded = BrowserTestUtils.waitForNewTab( + gBrowser, + `${manifestUrl}` + ); + const link = doc.querySelector(".js-manifest-json-link"); + link.click(); + const jsonTab = await onTabLoaded; + ok(jsonTab, "The manifest JSON was opened in a new tab"); + + // close the tabs + info("Closing the page tab."); + await BrowserTestUtils.removeTab(tab); + info("Closing the manifest JSON tab."); + await BrowserTestUtils.removeTab(jsonTab); +}); + +add_task(async function () { + info( + "Test that manifest page does not show a link for manifests embedded in a data url" + ); + const url = URL_ROOT_SSL + "resources/manifest/load-ok.html"; + + await enableApplicationPanel(); + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to load"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest loaded successfully"); + is( + doc.querySelector(".js-manifest-json-link"), + null, + "No JSON link is shown" + ); + + // close tab + info("Closing the tab"); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_manifest-reload.js b/devtools/client/application/test/browser/browser_application_panel_manifest-reload.js new file mode 100644 index 0000000000..4b8ab50985 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_manifest-reload.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the application panel refetches the page manifest when reloading + * or navigating to a new page + */ + +add_task(async function () { + await enableApplicationPanel(); + + info("Loading a page with no manifest"); + let url = URL_ROOT_SSL + "resources/manifest/load-no-manifest.html"; + const { panel, tab } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the 'no manifest' message to appear"); + await waitFor(() => doc.querySelector(".js-manifest-empty") !== null); + ok(true, "Manifest page displays a 'no manifest' message"); + + info("Navigating to a page with a manifest"); + url = URL_ROOT_SSL + "resources/manifest/load-ok.html"; + await navigateTo(url); + + info("Waiting for the manifest to show up"); + await waitFor(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest displayed successfully"); + + info("Navigating to a page with a manifest that fails to load"); + url = URL_ROOT_SSL + "resources/manifest/load-fail.html"; + await navigateTo(url); + + info("Waiting for the manifest to fail to load"); + await waitFor(() => doc.querySelector(".js-manifest-loaded-error") !== null); + ok(true, "Manifest page displays loading error"); + + info("Reloading"); + await navigateTo(url); + + info("Waiting for the manifest to fail to load"); + await waitFor(() => doc.querySelector(".js-manifest-loaded-error") !== null); + ok(true, "Manifest page displays loading error"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_open-links.js b/devtools/client/application/test/browser/browser_application_panel_open-links.js new file mode 100644 index 0000000000..d654873827 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_open-links.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Toolbox } = require("resource://devtools/client/framework/toolbox.js"); + +/** + * Check that links work when the devtools are detached in a separate window. + */ + +const TAB_URL = URL_ROOT + "resources/service-workers/empty.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, toolbox } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + // detach devtools in a separate window + await toolbox.switchHost(Toolbox.HostType.WINDOW); + + // click on the link and wait for the new tab to open + const onTabLoaded = BrowserTestUtils.waitForNewTab( + gBrowser, + "about:debugging#workers", + true + ); + doc.querySelector(".js-trusted-link").click(); + info("Opening link in a new tab."); + const newTab = await onTabLoaded; + + // We only need to check that newTab is truthy since + // BrowserTestUtils.waitForNewTab checks the URL. + ok(newTab, "The expected tab was opened."); + + info("Wait until the main about debugging container is available"); + await waitUntil(() => { + const aboutDebuggingDoc = newTab.linkedBrowser.contentDocument; + return aboutDebuggingDoc.querySelector(".app"); + }); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(newTab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_sidebar.js b/devtools/client/application/test/browser/browser_application_panel_sidebar.js new file mode 100644 index 0000000000..80b2fbfe09 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_sidebar.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the manifest is being properly shown + */ + +add_task(async function () { + info("Test that we are displaying correctly the sidebar"); + + await enableApplicationPanel(); + const { panel, tab, commands } = await openNewTabAndApplicationPanel(); + const doc = panel.panelWin.document; + + info("Waiting for the sidebar to be displayed"); + await waitUntil(() => doc.querySelector(".js-sidebar") !== null); + ok(true, "Sidebar is being displayed"); + + await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null); + ok(true, "Service Workers page was loaded per default."); + + // close the tab + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info("Test that we are displaying correctly the selected page - manifest"); + + await enableApplicationPanel(); + const { panel, tab, commands } = await openNewTabAndApplicationPanel(); + const doc = panel.panelWin.document; + + info("Select service worker page"); + selectPage(panel, "service-workers"); + await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null); + await unregisterAllWorkers(commands.client, doc); + + info("Select manifest page in the sidebar"); + const link = doc.querySelector(".js-sidebar-manifest"); + link.click(); + + await waitUntil(() => doc.querySelector(".js-manifest-page") !== null); + ok(true, "Manifest page was selected."); + + // close the tab + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + info( + "Test that we are displaying correctly the selected page - service workers" + ); + const url = URL_ROOT + "resources/manifest/load-ok.html"; + + await enableApplicationPanel(); + const { panel, tab, commands } = await openNewTabAndApplicationPanel(url); + const doc = panel.panelWin.document; + + selectPage(panel, "manifest"); + + info("Waiting for the manifest to load"); + await waitUntil(() => doc.querySelector(".js-manifest-page") !== null); + ok(true, "Manifest page was selected."); + + info("Select service worker page in the sidebar"); + const link = doc.querySelector(".js-sidebar-service-workers"); + link.click(); + + await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null); + ok(true, "Service workers page was selected."); + + // close the tab + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_start-service-worker.js b/devtools/client/application/test/browser/browser_application_panel_start-service-worker.js new file mode 100644 index 0000000000..1070f0f5af --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_start-service-worker.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/simple.html"; + +/** + * Tests that the Start button works for service workers who can be debugged + */ +add_task(async function () { + await enableApplicationPanel(); // this also enables SW debugging + + // Setting a low idle_timeout and idle_extended_timeout will allow the service worker + // to reach the STOPPED state quickly, which will allow us to test the start button. + // The default value is 30000 milliseconds. + info("Set a low service worker idle timeout"); + await pushPref("dom.serviceWorkers.idle_timeout", 1000); + await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000); + + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + await waitForWorkerRegistration(tab); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + info("Wait until the start button is displayed and enabled"); + const container = getWorkerContainers(doc)[0]; + await waitUntil(() => { + const button = container.querySelector(".js-start-button"); + return button && !button.disabled; + }); + + info("Click the button and wait for the worker to start"); + const button = container.querySelector(".js-start-button"); + button.click(); + + info("Wait until status 'Running' is displayed"); + await waitUntil(() => { + const statusEl = container.querySelector(".js-worker-status"); + return statusEl && statusEl.textContent === "Running"; + }); + ok(true, "Worker status is 'Running'"); + + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_target-switching.js b/devtools/client/application/test/browser/browser_application_panel_target-switching.js new file mode 100644 index 0000000000..ff8d521e30 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_target-switching.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test switching for the top-level target. + +// We use about:robots, because this page will run in the parent process. +// Navigating from about:robots to a regular content page will always trigger +// a target switch, with or without fission. +const PARENT_PROCESS_URI = "about:robots"; +const CONTENT_PROCESS_URI_WORKERS = + URL_ROOT + "resources/service-workers/simple.html"; +const CONTENT_PROCESS_URI_MANIFEST = + URL_ROOT + "resources/manifest/load-ok.html"; + +// test workers when target switching +add_task(async function () { + await enableApplicationPanel(); + + info("Open a page that runs in the parent process"); + const { panel, commands, tab } = await openNewTabAndApplicationPanel( + PARENT_PROCESS_URI + ); + const doc = panel.panelWin.document; + + info("Check for non-existing service worker"); + selectPage(panel, "service-workers"); + const isWorkerListEmpty = !!doc.querySelector(".js-registration-list-empty"); + ok(isWorkerListEmpty, "No Service Worker displayed"); + + info("Navigate to a page that runs in the child process"); + await navigateTo(CONTENT_PROCESS_URI_WORKERS); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + // close the tab + info("Closing the tab."); + await unregisterAllWorkers(commands.client, doc); + await BrowserTestUtils.removeTab(tab); +}); + +// test manifest when target switching +add_task(async function () { + await enableApplicationPanel(); + + info("Open a page that runs in the parent process"); + const { panel, tab } = await openNewTabAndApplicationPanel( + PARENT_PROCESS_URI + ); + const doc = panel.panelWin.document; + + info("Waiting for the 'no manifest' message to appear"); + selectPage(panel, "manifest"); + await waitUntil(() => doc.querySelector(".js-manifest-empty") !== null); + + info("Navigate to a page that runs in the child process"); + await navigateTo(CONTENT_PROCESS_URI_MANIFEST); + + info("Waiting for the manifest to load"); + selectPage(panel, "manifest"); + await waitUntil(() => doc.querySelector(".js-manifest") !== null); + ok(true, "Manifest loaded successfully"); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js new file mode 100644 index 0000000000..fe95dabd5e --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/simple.html"; + +// check telemetry for debugging a service worker +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab, toolbox, commands } = await openNewTabAndApplicationPanel( + TAB_URL + ); + + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + setupTelemetryTest(); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + const container = getWorkerContainers(doc)[0]; + info("Wait until the debug link is displayed"); + await waitUntil(() => { + return container.querySelector(".js-inspect-link"); + }); + + info("Click on the debug link and wait for debugger to be ready"); + const debugLink = container.querySelector(".js-inspect-link"); + debugLink.click(); + await waitUntil(() => toolbox.getPanel("jsdebugger")); + + const events = getTelemetryEvents("jsdebugger"); + const openToolboxEvent = events.find(event => event.method == "enter"); + ok(openToolboxEvent.session_id > 0, "Event has a valid session id"); + is( + openToolboxEvent.start_state, + "application", + "Event has the 'application' start state" + ); + + // clean up and close the tab + await unregisterAllWorkers(commands.client, doc); + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js new file mode 100644 index 0000000000..c200c38c17 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function () { + await enableApplicationPanel(); + + const TAB_URL = URL_ROOT + "resources/service-workers/empty.html"; + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + setupTelemetryTest(); + + // make sure the default page is opened and then select a different one + await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null); + ok(true, "Service Workers page was loaded per default."); + selectPage(panel, "manifest"); + + checkTelemetryEvent({ method: "select_page", page_type: "manifest" }); + + // close the tab + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js new file mode 100644 index 0000000000..235ce3060d --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/simple.html"; + +// check telemetry for starting a service worker +add_task(async function () { + info("Set a low service worker idle timeout"); + await pushPref("dom.serviceWorkers.idle_timeout", 1000); + await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000); + + await enableApplicationPanel(); + + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + await waitForWorkerRegistration(tab); + + setupTelemetryTest(); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + info("Wait until the start button is displayed and enabled"); + const container = getWorkerContainers(doc)[0]; + await waitUntil(() => { + const button = container.querySelector(".js-start-button"); + return button && !button.disabled; + }); + + info("Click the start button"); + const button = container.querySelector(".js-start-button"); + button.click(); + + checkTelemetryEvent({ method: "start_worker" }); + + // clean up and close the tab + await unregisterAllWorkers(commands.client, doc); + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js b/devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js new file mode 100644 index 0000000000..aa6d35f946 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/simple.html"; + +// check telemetry for unregistering a service worker +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + setupTelemetryTest(); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + const workerContainer = getWorkerContainers(doc)[0]; + + info("Wait until the unregister button is displayed for the service worker"); + await waitUntil(() => workerContainer.querySelector(".js-unregister-button")); + info("Click the unregister button"); + const button = workerContainer.querySelector(".js-unregister-button"); + button.click(); + + checkTelemetryEvent({ method: "unregister_worker" }); + + // clean up and close the tab + await unregisterAllWorkers(commands.client, doc); + info("Closing the tab."); + await commands.client.waitForRequestsToSettle(); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_unregister-worker.js b/devtools/client/application/test/browser/browser_application_panel_unregister-worker.js new file mode 100644 index 0000000000..8f6c4a5793 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_unregister-worker.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/simple.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + const workerContainer = getWorkerContainers(doc)[0]; + + info("Wait until the unregister button is displayed for the service worker"); + await waitUntil(() => workerContainer.querySelector(".js-unregister-button")); + info("Click the unregister button"); + const button = workerContainer.querySelector(".js-unregister-button"); + button.click(); + info("Wait until the service worker is removed from the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 0); + ok(true, "Service worker list is empty"); + + // just in case cleanup + await unregisterAllWorkers(commands.client, doc); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js b/devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js new file mode 100644 index 0000000000..fc6f0819b6 --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/debug.html"; +const SW_URL = URL_ROOT + "resources/service-workers/debug-sw.js"; + +add_task(async function () { + await enableApplicationPanel(); + + // disable service worker debugging + await pushPref( + "devtools.debugger.features.windowless-service-workers", + false + ); + + const { panel, tab, commands } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 1); + + const container = getWorkerContainers(doc)[0]; + info("Wait until the inspect link is displayed"); + await waitUntil(() => { + return container.querySelector(".js-inspect-link"); + }); + + info("Click on the inspect link and wait for a new view-source: tab open"); + // click on the link and wait for the new tab to open + const onTabLoaded = BrowserTestUtils.waitForNewTab( + gBrowser, + `view-source:${SW_URL}` + ); + const inspectLink = container.querySelector(".js-inspect-link"); + inspectLink.click(); + + const sourceTab = await onTabLoaded; + ok(sourceTab, "The service worker source was opened in a new tab"); + + // clean up + await unregisterAllWorkers(commands.client, doc); + // close the tabs + info("Closing the tabs."); + await BrowserTestUtils.removeTab(sourceTab); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/browser_application_panel_worker-states.js b/devtools/client/application/test/browser/browser_application_panel_worker-states.js new file mode 100644 index 0000000000..54446506fa --- /dev/null +++ b/devtools/client/application/test/browser/browser_application_panel_worker-states.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TAB_URL = URL_ROOT + "resources/service-workers/controlled-install.html"; + +add_task(async function () { + await enableApplicationPanel(); + + const { panel, tab } = await openNewTabAndApplicationPanel(TAB_URL); + const doc = panel.panelWin.document; + + selectPage(panel, "service-workers"); + + info("Check for non-existing service worker"); + const isWorkerListEmpty = !!doc.querySelector(".js-registration-list-empty"); + ok(isWorkerListEmpty, "No Service Worker displayed"); + + info("Register a service worker with a controlled install in the page."); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + content.wrappedJSObject.registerServiceWorker(); + }); + + info("Wait until the service worker appears in the application panel"); + await waitUntil(() => !!getWorkerContainers(doc).length); + info("Wait until the 'Installing' state is displayed"); + await waitUntil(() => { + const containers = getWorkerContainers(doc); + if (containers.length === 0) { + return false; + } + + const stateEl = containers[0].querySelector(".js-worker-status"); + return stateEl.textContent.toLowerCase() === "installing"; + }); + + info("Allow the service worker to complete installation"); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + content.wrappedJSObject.installServiceWorker(); + }); + + info("Wait until the 'running' state is displayed"); + await waitUntil(() => { + const workerContainer = getWorkerContainers(doc)[0]; + const stateEl = workerContainer.querySelector(".js-worker-status"); + return stateEl.textContent.toLowerCase() === "running"; + }); + + info("Unregister the service worker"); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + const registration = await content.wrappedJSObject.sw; + registration.unregister(); + }); + + info("Wait until the service worker is removed from the application panel"); + await waitUntil(() => getWorkerContainers(doc).length === 0); + + // close the tab + info("Closing the tab."); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/devtools/client/application/test/browser/head.js b/devtools/client/application/test/browser/head.js new file mode 100644 index 0000000000..6b7e67ff8d --- /dev/null +++ b/devtools/client/application/test/browser/head.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env browser */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ + +"use strict"; + +// Load the shared-head file first. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); + +/** + * Set all preferences needed to enable service worker debugging and testing. + */ +async function enableServiceWorkerDebugging() { + // Enable service workers. + await pushPref("dom.serviceWorkers.enabled", true); + // Accept workers from mochitest's http. + await pushPref("dom.serviceWorkers.testing.enabled", true); + // Force single content process, see Bug 1231208 for the SW refactor that should enable + // SW debugging in multi-e10s. + await pushPref("dom.ipc.processCount", 1); + + // Enable service workers in the debugger + await pushPref("devtools.debugger.features.windowless-service-workers", true); + // Disable randomly spawning processes during tests + await pushPref("dom.ipc.processPrelaunch.enabled", false); + + // Wait for dom.ipc.processCount to be updated before releasing processes. + Services.ppmm.releaseCachedProcesses(); +} + +async function enableApplicationPanel() { + // FIXME bug 1575427 this rejection is very common. + const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" + ); + PromiseTestUtils.allowMatchingRejectionsGlobally( + /this._frontCreationListeners is null/ + ); + + // Enable all preferences related to service worker debugging. + await enableServiceWorkerDebugging(); + + // Enable web manifest processing. + Services.prefs.setBoolPref("dom.manifest.enabled", true); + + // Enable application panel in DevTools. + await pushPref("devtools.application.enabled", true); +} + +function setupTelemetryTest() { + // Reset all the counts + Services.telemetry.clearEvents(); + + // Ensure no events have been logged + const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS; + const snapshot = Services.telemetry.snapshotEvents(ALL_CHANNELS, true); + ok(!snapshot.parent, "No events have been logged for the main process"); +} + +function getTelemetryEvents(objectName) { + // read the requested events only + const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS; + const snapshot = Services.telemetry.snapshotEvents(ALL_CHANNELS, true); + // filter and transform the event data so the relevant info is in a single object: + // { method: "...", extraField: "...", anotherExtraField: "...", ... } + const events = snapshot.parent + .filter(event => event[1] === "devtools.main" && event[3] === objectName) + .map(event => ({ method: event[2], ...event[5] })); + + return events; +} + +function checkTelemetryEvent(expectedEvent, objectName = "application") { + info("Check telemetry event"); + const events = getTelemetryEvents(objectName); + + // assert we only got 1 event with a valid session ID + is(events.length, 1, "There was only 1 event logged"); + const [event] = events; + ok(event.session_id > 0, "There is a valid session_id in the event"); + + // assert expected data + Assert.deepEqual(event, { ...expectedEvent, session_id: event.session_id }); +} + +function getWorkerContainers(doc) { + return doc.querySelectorAll(".js-sw-container"); +} + +async function openNewTabAndApplicationPanel(url) { + const tab = await addTab(url); + + const toolbox = await gDevTools.showToolboxForTab(tab, { + toolId: "application", + }); + const panel = toolbox.getCurrentPanel(); + const target = toolbox.target; + const commands = toolbox.commands; + return { panel, tab, target, toolbox, commands }; +} + +async function unregisterAllWorkers(client, doc) { + // This method is declared in shared-head.js + await unregisterAllServiceWorkers(client); + + info("Wait for service workers to disappear from the UI"); + waitUntil(() => getWorkerContainers(doc).length === 0); +} + +async function waitForWorkerRegistration(swTab) { + info("Wait until the registration appears on the window"); + const swBrowser = swTab.linkedBrowser; + await asyncWaitUntil(async () => + SpecialPowers.spawn(swBrowser, [], function () { + return !!content.wrappedJSObject.getRegistration(); + }) + ); +} + +function selectPage(panel, page) { + /** + * Select a page by simulating a user click in the sidebar. + * @param {string} page The page we want to select (see `PAGE_TYPES`) + **/ + info(`Selecting application page: ${page}`); + const doc = panel.panelWin.document; + const navItem = doc.querySelector(`.js-sidebar-${page}`); + navItem.click(); +} diff --git a/devtools/client/application/test/browser/resources/manifest/icon.svg b/devtools/client/application/test/browser/resources/manifest/icon.svg new file mode 100644 index 0000000000..bfed2982bc --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/icon.svg @@ -0,0 +1,4 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 953.37 984"><defs><linearGradient id="linear-gradient" x1="-14706.28" y1="9250.14" x2="-14443.04" y2="9250.14" gradientTransform="matrix(0.76, 0.03, 0.05, -1.12, 11485.47, 11148)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.1" stop-color="#0092f8"/><stop offset="0.31" stop-color="#00abeb"/><stop offset="0.52" stop-color="#00bee1"/><stop offset="0.75" stop-color="#00c8dc"/><stop offset="1" stop-color="#00ccda"/></linearGradient><radialGradient id="radial-gradient" cx="-7588.66" cy="8866.53" r="791.23" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0.02" stop-color="#005fe7"/><stop offset="0.18" stop-color="#0042b4"/><stop offset="0.32" stop-color="#002989"/><stop offset="0.4" stop-color="#002079"/><stop offset="0.47" stop-color="#131d78"/><stop offset="0.66" stop-color="#3b1676"/><stop offset="0.75" stop-color="#4a1475"/></radialGradient><linearGradient id="linear-gradient-2" x1="539.64" y1="254.8" x2="348.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 1, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000f43" stop-opacity="0.4"/><stop offset="0.48" stop-color="#001962" stop-opacity="0.17"/><stop offset="1" stop-color="#002079" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-3" x1="540.64" y1="254.8" x2="349.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" href="#linear-gradient-2"/><linearGradient id="linear-gradient-4" x1="-8367.12" y1="7348.87" x2="-8482.36" y2="7357.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#812cc9"/><stop offset="1" stop-color="#005fe7"/></linearGradient><linearGradient id="linear-gradient-5" x1="-8449.89" y1="7496.97" x2="-8341.94" y2="7609.09" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.05" stop-color="#005fe7"/><stop offset="0.18" stop-color="#065de6"/><stop offset="0.35" stop-color="#1856e1"/><stop offset="0.56" stop-color="#354adb"/><stop offset="0.78" stop-color="#5d3ad1"/><stop offset="0.95" stop-color="#812cc9"/></linearGradient><linearGradient id="linear-gradient-6" x1="-8653.41" y1="7245.3" x2="-8422.52" y2="7244.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002079"/><stop offset="0.99" stop-color="#a238ff"/></linearGradient><radialGradient id="radial-gradient-2" cx="644.11" cy="599.83" fx="785.0454815336918" fy="470.6889181532662" r="793.95" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#00fdff"/><stop offset="0.26" stop-color="#0af1ff"/><stop offset="0.37" stop-color="#23d2ff"/><stop offset="0.52" stop-color="#4da0ff"/><stop offset="0.69" stop-color="#855bff"/><stop offset="0.77" stop-color="#a238ff"/><stop offset="0.81" stop-color="#a738fd"/><stop offset="0.86" stop-color="#b539f9"/><stop offset="0.9" stop-color="#cd39f1"/><stop offset="0.96" stop-color="#ee3ae6"/><stop offset="0.98" stop-color="#ff3be0"/></radialGradient><linearGradient id="linear-gradient-7" x1="-7458.97" y1="9093.17" x2="-7531.06" y2="8282.84" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00"/><stop offset="0.1" stop-color="#00e244"/><stop offset="0.22" stop-color="#00d694"/><stop offset="0.31" stop-color="#00cfc7"/><stop offset="0.35" stop-color="#00ccda"/><stop offset="0.42" stop-color="#0bc2dd" stop-opacity="0.92"/><stop offset="0.57" stop-color="#29a7e4" stop-opacity="0.72"/><stop offset="0.77" stop-color="#597df0" stop-opacity="0.4"/><stop offset="1" stop-color="#9448ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-8" x1="-8926.61" y1="7680.53" x2="-8790.14" y2="7680.53" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005fe7"/><stop offset="0.46" stop-color="#0071f3" stop-opacity="0.51"/><stop offset="0.83" stop-color="#007efc" stop-opacity="0.14"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><radialGradient id="radial-gradient-3" cx="-8914.62" cy="7721.05" r="165.97" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.63" stop-color="#ffe302" stop-opacity="0"/><stop offset="0.67" stop-color="#ffe302" stop-opacity="0.05"/><stop offset="0.75" stop-color="#ffe302" stop-opacity="0.19"/><stop offset="0.86" stop-color="#ffe302" stop-opacity="0.4"/><stop offset="0.99" stop-color="#ffe302" stop-opacity="0.7"/></radialGradient><linearGradient id="linear-gradient-9" x1="214.02" y1="2032.47" x2="96.19" y2="2284.31" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, -250.1, 2306.29)" gradientUnits="userSpaceOnUse"><stop offset="0.19" stop-color="#4a1475" stop-opacity="0.5"/><stop offset="0.62" stop-color="#2277ac" stop-opacity="0.23"/><stop offset="0.94" stop-color="#00ccda" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-10" x1="-38.44" y1="278.18" x2="55.67" y2="171.29" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0.01" stop-color="#002079" stop-opacity="0.5"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-11" x1="142.45" y1="96.25" x2="142.5" y2="149.68" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a1475" stop-opacity="0.9"/><stop offset="0.18" stop-color="#6720a2" stop-opacity="0.6"/><stop offset="0.38" stop-color="#812acb" stop-opacity="0.34"/><stop offset="0.57" stop-color="#9332e8" stop-opacity="0.15"/><stop offset="0.76" stop-color="#9e36f9" stop-opacity="0.04"/><stop offset="0.93" stop-color="#a238ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-12" x1="620.52" y1="947.88" x2="926.18" y2="264.39" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00" stop-opacity="0"/><stop offset="0.28" stop-color="#00dc6d" stop-opacity="0.5"/><stop offset="0.5" stop-color="#00d1bb" stop-opacity="0.86"/><stop offset="0.6" stop-color="#00ccda"/><stop offset="0.68" stop-color="#04c9db"/><stop offset="0.75" stop-color="#0fc1df"/><stop offset="0.83" stop-color="#23b2e6"/><stop offset="0.9" stop-color="#3e9ef0"/><stop offset="0.98" stop-color="#6184fc"/><stop offset="0.99" stop-color="#6680fe"/></linearGradient><linearGradient id="linear-gradient-13" x1="680.88" y1="554.79" x2="536.1" y2="166.04" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.04" stop-color="#0083ff" stop-opacity="0.92"/><stop offset="0.14" stop-color="#0083ff" stop-opacity="0.71"/><stop offset="0.26" stop-color="#0083ff" stop-opacity="0.52"/><stop offset="0.37" stop-color="#0083ff" stop-opacity="0.36"/><stop offset="0.49" stop-color="#0083ff" stop-opacity="0.23"/><stop offset="0.61" stop-color="#0083ff" stop-opacity="0.13"/><stop offset="0.73" stop-color="#0083ff" stop-opacity="0.06"/><stop offset="0.86" stop-color="#0083ff" stop-opacity="0.01"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient></defs><title>firefox-logo-nightly</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Layer_2-2" data-name="Layer 2"><g id="Firefox"><path d="M770.28,91.56c-23.95,27.88-35.1,90.64-10.82,154.26s61.5,49.8,84.7,114.67c30.62,85.6,16.37,200.59,16.37,200.59s36.81,106.61,62.47-6.63C979.79,341.74,770.28,143.94,770.28,91.56Z" style="fill:url(#linear-gradient)"/><path id="_Path_" data-name=" Path " d="M476.92,972.83c245.24,0,443.9-199.74,443.9-446s-198.66-446-443.66-446S33.5,280.51,33.5,526.8C33,773.33,231.92,972.83,476.92,972.83Z" style="fill:url(#radial-gradient)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-2)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-3)"/><path d="M711.1,866.71c162.87-18.86,235-186.7,142.38-190C769.85,674,634,875.61,711.1,866.71Z" style="fill:url(#linear-gradient-4)"/><path d="M865.21,642.42C977.26,577.21,948,436.34,948,436.34s-43.25,50.24-72.62,130.32C846.4,646,797.84,681.81,865.21,642.42Z" style="fill:url(#linear-gradient-5)"/><path d="M509.47,950.06C665.7,999.91,800,876.84,717.21,835.74,642,798.68,435.32,926.49,509.47,950.06Z" style="fill:url(#linear-gradient-6)"/><path d="M638.58,21.42l.53-.57A1.7,1.7,0,0,0,638.58,21.42ZM876.85,702.23c3.8-5.36,8.94-22.53,13.48-30.21,27.58-44.52,27.78-80,27.78-80.84,16.66-83.22,15.15-117.2,4.9-180-8.25-50.6-44.32-123.09-75.57-158-32.2-36-9.51-24.25-40.69-50.52-27.33-30.29-53.82-60.29-68.25-72.36C634.22,43.09,636.57,24.58,638.58,21.42c-.34.37-.84.92-1.47,1.64C635.87,18.14,635,14,635,14s-57,57-69,152c-7.83,62,15.38,126.68,49,168a381.62,381.62,0,0,0,59,58h0c25.4,36.48,39.38,81.49,39.38,129.91,0,121.24-98.34,219.53-219.65,219.53a220.14,220.14,0,0,1-49.13-5.52c-57.24-10.92-90.3-39.8-106.78-59.41-9.45-11.23-13.46-19.42-13.46-19.42,51.28,18.37,108,14.53,142.47-4.52,34.75-19.26,55.77-33.55,72.84-27.92,16.82,5.61,30.21-10.67,18.2-27.54-11.77-16.85-42.4-41-87.88-34.29-34.79,5.07-66.66,29.76-112.24,5.84a97.34,97.34,0,0,1-8.55-5c-3-1.77,9.77,2.69,6.79.68-8.87-4.32-24.57-13.73-28.64-17.07-.68-.56,6.88,2.16,6.2,1.6-42.62-31.45-37.3-52.69-36-66,1.07-10.66,8.81-24.32,21.86-29.86,6.3,3.08,10.23,5.43,10.23,5.43s-2.69-4.92-4.14-7.51c.51-.19,1-.15,1.5-.34,5.16,2.23,16.58,8,22.59,11.57,7.83,4.95,10.32,9.36,10.32,9.36s2.06-1,.54-5.33c-.56-1.77-2.93-7.39-10.68-13.07h.48a91.65,91.65,0,0,1,13.13,8.17c2.19-7.12,6.12-14.56,5.25-27.86-.53-9.35-.28-11.78-2.12-15.39-1.65-3.1.92-4.31,3.78-1.09a29.73,29.73,0,0,0-2.44-7.34v-.24c3.57-11.14,75.53-40.12,80.77-43.51a70.24,70.24,0,0,0,21.17-20.63c4-5.72,7-13.73,7.75-25.89.25-5.48-1.44-9.82-20.5-14-11.44-2.49-29.14-4.91-56.43-7.47-19.9-1.76-31.58-14.68-38.21-26.6-1.21-2.57-2.45-4.9-3.68-7.22a53.41,53.41,0,0,1-2.83-8.36,158.47,158.47,0,0,1,61.28-76.06c1.6-1.31-6.4.33-4.8-1,1.87-1.52,14.06-5.93,16.37-6.92,2.81-1.19-12-6.84-25.16-5.47-13.36,1.35-16.19,2.78-23.32,5.49,3-2.64,12.37-6.1,10.16-6.08-14.4,2-32.3,9.48-47.6,18a9.72,9.72,0,0,1,.92-4.31c-7.13,2.71-24.64,13.67-29.73,23a39.79,39.79,0,0,0,.29-5.35,88.55,88.55,0,0,0-14.6,13.7l-.27.22C258.14,196,221.75,195,191,201.72c-6.74-6.06-17.57-15.23-32.89-45.4-1-1.82-1.6,3.75-2.4,2-6-13.81-9.55-36.44-9-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.87-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.37,12.62-1.38,4-3.32,6.27-4.56,11.29l-.29.46c-.1-1.48.37-6.08,0-5.14A235.4,235.4,0,0,0,95.34,186c-5.49,18-11.88,42.61-12.89,74.57-.24,2.42,0,5.14-.25,7.32-13,14.83-21.86,27.39-25.2,33.91-16.81,26-35.33,66.44-53.29,130.46a319.35,319.35,0,0,1,28.54-50C17.32,416.25,2.89,469.62,0,551.8a436.92,436.92,0,0,1,13.87-50.24C11.29,556.36,17.68,624.3,52.32,701c20.57,45,67.92,136.6,183.62,208h0s39.36,29.3,107,51.26c5,1.81,10.06,3.6,15.23,5.33q-2.43-1-4.71-2A484.9,484.9,0,0,0,492.27,984c175.18.15,226.85-70.2,226.85-70.2l-.51.38q3.71-3.49,7.14-7.26c-27.64,26.08-90.75,27.84-114.3,26,40.22-11.81,66.69-21.81,118.17-41.52q9-3.36,18.48-7.64l2-.94c1.25-.58,2.49-1.13,3.75-1.74a349.3,349.3,0,0,0,70.26-44c51.7-41.3,63-81.56,68.83-108.1-.82,2.54-3.37,8.47-5.17,12.32-13.31,28.48-42.84,46-74.91,61a689.05,689.05,0,0,0,42.38-62.44C865.77,729.39,869,713.15,876.85,702.23Z" style="fill:url(#radial-gradient-2)"/><path d="M813.92,801c21.08-23.24,40-49.82,54.35-80,36.9-77.58,94-206.58,49-341.31C881.77,273.22,833,215,771.11,158.12,670.56,65.76,642.48,24.52,642.48,0c0,0-116.09,129.41-65.74,264.38s153.46,130,221.68,270.87c80.27,165.74-64.95,346.61-185,397.24,7.35-1.63,267-60.38,280.61-208.88C893.68,726.34,887.83,767.41,813.92,801Z" style="fill:url(#linear-gradient-7)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="fill:url(#linear-gradient-8)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="opacity:0.5;isolation:isolate;fill:url(#radial-gradient-3)"/><path d="M158.31,156.47c-1-1.82-1.6,3.75-2.4,2-6-13.81-9.58-36.2-8.72-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.86-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.35,12.38-1.65,4.24-3.35,6.52-4.61,11.77-.39,1.43.39-6.32,0-5.38C84.72,201.68,80.19,271,82.69,268,133.17,214.14,191,201.36,191,201.36c-6.15-4.53-19.53-17.63-32.7-44.89Z" style="fill:url(#linear-gradient-9)"/><path d="M349.84,720.1c-69.72-29.77-149-71.75-146-167.14C207.92,427.35,321,452.18,321,452.18c-4.27,1-15.68,9.16-19.72,17.82-4.27,10.83-12.07,35.28,11.55,60.9,37.09,40.19-76.2,95.36,98.66,199.57,4.41,2.4-41-1.43-61.64-10.36Z" style="fill:url(#linear-gradient-10)"/><path d="M325.07,657.5c49.44,17.21,107,14.19,141.52-4.86,23.09-12.85,52.7-33.43,70.92-28.35-15.78-6.24-27.73-9.15-42.1-9.86-2.45,0-5.38,0-8-.32a136,136,0,0,0-15.76.86c-8.9.82-18.77,6.43-27.74,5.53-.48,0,8.7-3.77,8-3.61-4.75,1-9.92,1.21-15.37,1.88-3.47.39-6.45.82-9.89,1-103,8.73-190-55.81-190-55.81-7.41,25,33.17,74.3,88.52,93.57Z" style="opacity:0.5;isolation:isolate;fill:url(#linear-gradient-11)"/><path d="M813.74,801.65c104.16-102.27,156.86-226.58,134.58-366,0,0,8.9,71.5-24.85,144.63,16.21-71.39,18.1-160.11-25-252C841,205.64,746.45,141.11,710.35,114.19,655.66,73.4,633,31.87,632.57,23.3c-16.34,33.48-65.77,148.2-5.31,247,56.64,92.56,145.86,120,208.33,205C950.67,631.67,813.74,801.65,813.74,801.65Z" style="fill:url(#linear-gradient-12)"/><path d="M798.81,535.55C762.41,460.35,717,427.55,674,392c5,7,6.23,9.47,9,14,37.83,40.32,93.61,138.66,53.11,262.11C659.88,900.48,355,791.06,323,760.32,335.93,894.81,561,959.16,707.6,872,791,793,858.47,658.79,798.81,535.55Z" style="fill:url(#linear-gradient-13)"/></g></g></g></g></svg> diff --git a/devtools/client/application/test/browser/resources/manifest/load-fail.html b/devtools/client/application/test/browser/resources/manifest/load-fail.html new file mode 100644 index 0000000000..180c42a7b5 --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-fail.html @@ -0,0 +1,9 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>Manifest 404 not found</title> + <link rel="manifest" href='nowhere.json'> +</head> +<body> +<h1>Manifest error 404 not found</h1> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/load-no-manifest.html b/devtools/client/application/test/browser/resources/manifest/load-no-manifest.html new file mode 100644 index 0000000000..aeabc8a0cb --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-no-manifest.html @@ -0,0 +1,8 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>No manifest link</title> +</head> +<body> +<h1>No manifest <code>link</code> tag.</h1> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-icons.html b/devtools/client/application/test/browser/resources/manifest/load-ok-icons.html new file mode 100644 index 0000000000..539e5d2247 --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-ok-icons.html @@ -0,0 +1,9 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>Manifest successful load (with icons)</title> + <link rel="manifest" href='data:application/manifest+json,{"name": "Foo", "background_color": "%23ff0000", "icons": [{ "sizes": "128x128", "src": "http://example.com/browser/devtools/client/application/test/browser/resources/manifest/icon.svg", "type": "image/svg"}]}'> +</head> +<body> +<h1>Manifest OK (with icons)</h1> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html b/devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html new file mode 100644 index 0000000000..95ad22b609 --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html @@ -0,0 +1,10 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>Manifest successful load with a warning</title> + <link rel="manifest" href='data:application/manifest+json,{"name": "Foo}'> +</head> +<body> +<h1>Manifest OK with validation errors</h1> +<p>The manifest has invalid JSON</p> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html b/devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html new file mode 100644 index 0000000000..f336f409e3 --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html @@ -0,0 +1,9 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>Successful load for a linked manifest</title> + <link rel="manifest" href='manifest.json'> +</head> +<body> +<h1>Manifest OK (linked manifest)</h1> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html b/devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html new file mode 100644 index 0000000000..467d6c3e70 --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html @@ -0,0 +1,10 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>Manifest successful load with a warning</title> + <link rel="manifest" href='data:application/manifest+json,{"name": "Foo", "background_color": 42}'> +</head> +<body> +<h1>Manifest OK with validation warnings</h1> +<p><code>background_color</code> does not contain a valid CSS color.</p> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/load-ok.html b/devtools/client/application/test/browser/resources/manifest/load-ok.html new file mode 100644 index 0000000000..1e6f5de59e --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/load-ok.html @@ -0,0 +1,9 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title>Manifest successful load</title> + <link rel="manifest" href='data:application/manifest+json,{"name": "Foo", "background_color": "%23ff0000"}'> +</head> +<body> +<h1>Manifest OK</h1> +</body> diff --git a/devtools/client/application/test/browser/resources/manifest/manifest.json b/devtools/client/application/test/browser/resources/manifest/manifest.json new file mode 100644 index 0000000000..0bc7bb50a3 --- /dev/null +++ b/devtools/client/application/test/browser/resources/manifest/manifest.json @@ -0,0 +1,3 @@ +{ + "name": "Foo" +} diff --git a/devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js b/devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js new file mode 100644 index 0000000000..d0a70a5312 --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Copied from shared-head.js +function waitUntil(predicate, interval = 10) { + if (predicate()) { + return Promise.resolve(true); + } + return new Promise(resolve => { + setTimeout(function () { + waitUntil(predicate, interval).then(() => resolve(true)); + }, interval); + }); +} + +// this flag will be flipped externally from controlled-install.html +// by sending a message event to the worker +let canInstall = false; +self.addEventListener("message", event => { + if (event.data === "install-service-worker") { + canInstall = true; + } +}); + +self.addEventListener("install", event => { + event.waitUntil(waitUntil(() => canInstall)); +}); diff --git a/devtools/client/application/test/browser/resources/service-workers/controlled-install.html b/devtools/client/application/test/browser/resources/service-workers/controlled-install.html new file mode 100644 index 0000000000..300ee1fde7 --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/controlled-install.html @@ -0,0 +1,27 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test</title> +</head> +<body> +<script type="text/javascript"> +"use strict"; + +let registration; + +window.registerServiceWorker = async function() { + registration = await navigator.serviceWorker.register( + "controlled-install-sw.js" + ); + window.sw = registration; +}; + +window.installServiceWorker = function() { + registration.installing.postMessage("install-service-worker"); +}; +</script> +</body> +</html> diff --git a/devtools/client/application/test/browser/resources/service-workers/debug-sw.js b/devtools/client/application/test/browser/resources/service-workers/debug-sw.js new file mode 100644 index 0000000000..31f0b1bdd2 --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/debug-sw.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +self.addEventListener("activate", event => { + event.waitUntil(self.clients.claim()); +}); + +self.onfetch = function (event) { + const url = event.request.url; + + const response = url.endsWith("test") + ? new Response("lorem ipsum", { statusText: "OK" }) + : fetch(event.request); + + event.respondWith(response); +}; diff --git a/devtools/client/application/test/browser/resources/service-workers/debug.html b/devtools/client/application/test/browser/resources/service-workers/debug.html new file mode 100644 index 0000000000..f0f16858fd --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/debug.html @@ -0,0 +1,25 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test</title> +</head> +<body> +<script type="text/javascript"> +"use strict"; +window.sw = navigator.serviceWorker.register("debug-sw.js"); + +/* exported fetchFromWorker */ +async function fetchFromWorker() { + const response = await fetch("test"); + const text = await response.text(); + console.log(`Response from worker: ${text}`); +} +</script> + +<p>This page has a <code>fetchFromWorker()</code> function.</p> +</body> +</html> diff --git a/devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html b/devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html new file mode 100644 index 0000000000..def300da65 --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html @@ -0,0 +1,19 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test</title> +</head> +<body> +<script type="text/javascript"> +"use strict"; + +window.registerServiceWorker = function() { + window.sw = navigator.serviceWorker.register("empty-sw.js"); +}; + +</script> +</body> +</html> diff --git a/devtools/client/application/test/browser/resources/service-workers/empty-sw.js b/devtools/client/application/test/browser/resources/service-workers/empty-sw.js new file mode 100644 index 0000000000..5d33297056 --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/empty-sw.js @@ -0,0 +1,4 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Empty, just test registering. diff --git a/devtools/client/application/test/browser/resources/service-workers/empty.html b/devtools/client/application/test/browser/resources/service-workers/empty.html new file mode 100644 index 0000000000..02373ca02e --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/empty.html @@ -0,0 +1,11 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test (no worker, empty page)</title> +</head> +<body></body> +</html> diff --git a/devtools/client/application/test/browser/resources/service-workers/scope-page.html b/devtools/client/application/test/browser/resources/service-workers/scope-page.html new file mode 100644 index 0000000000..eed5bc82ed --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/scope-page.html @@ -0,0 +1,19 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test</title> +</head> +<body> +<script type="text/javascript"> +"use strict"; + +window.sw = navigator.serviceWorker.register("empty-sw.js", { + scope: "./scope-page.html", +}); + +</script> +</body> +</html> diff --git a/devtools/client/application/test/browser/resources/service-workers/simple-unicode.html b/devtools/client/application/test/browser/resources/service-workers/simple-unicode.html new file mode 100644 index 0000000000..51e17b7fec --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/simple-unicode.html @@ -0,0 +1,15 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test</title> +</head> +<body> +<script type="text/javascript"> +"use strict"; +window.sw = navigator.serviceWorker.register("empty-sw.js?q=日本"); +</script> +</body> +</html> diff --git a/devtools/client/application/test/browser/resources/service-workers/simple.html b/devtools/client/application/test/browser/resources/service-workers/simple.html new file mode 100644 index 0000000000..88dc00aff0 --- /dev/null +++ b/devtools/client/application/test/browser/resources/service-workers/simple.html @@ -0,0 +1,32 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Service worker test</title> +</head> +<body> +<script type="text/javascript"> +"use strict"; + +let registration; + +const registerServiceWorker = async function() { + try { + registration = await navigator.serviceWorker.register("empty-sw.js"); + dump("Empty service worker registered\n"); + } catch (e) { + dump("Empty service worker not registered: " + e + "\n"); + } +}; + +// Helper called from head.js to unregister the service worker. +window.getRegistration = function() { + return registration; +}; +// Register the service worker. +registerServiceWorker(); +</script> +</body> +</html> diff --git a/devtools/client/application/test/node/.eslintrc.js b/devtools/client/application/test/node/.eslintrc.js new file mode 100644 index 0000000000..ffb3e70473 --- /dev/null +++ b/devtools/client/application/test/node/.eslintrc.js @@ -0,0 +1,10 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +module.exports = { + env: { + jest: true, + }, +}; diff --git a/devtools/client/application/test/node/actions/actions_application_panel-manifest.test.js b/devtools/client/application/test/node/actions/actions_application_panel-manifest.test.js new file mode 100644 index 0000000000..a8d4844f4c --- /dev/null +++ b/devtools/client/application/test/node/actions/actions_application_panel-manifest.test.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + MANIFEST_NO_ISSUES, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +const { + ManifestDevToolsError, + services, +} = require("resource://devtools/client/application/src/modules/application-services.js"); + +const { + FETCH_MANIFEST_FAILURE, + FETCH_MANIFEST_START, + FETCH_MANIFEST_SUCCESS, +} = require("resource://devtools/client/application/src/constants.js"); + +const { + fetchManifest, +} = require("resource://devtools/client/application/src/actions/manifest.js"); + +describe("Manifest actions: fetchManifest", () => { + it("dispatches a START - SUCCESS sequence when fetching is OK", async () => { + const fetchManifestSpy = jest + .spyOn(services, "fetchManifest") + .mockResolvedValue(MANIFEST_NO_ISSUES); + + const store = setupStore({}); + await store.dispatch(fetchManifest()); + + expect(store.getActions()).toEqual([ + { type: FETCH_MANIFEST_START }, + { type: FETCH_MANIFEST_SUCCESS, manifest: MANIFEST_NO_ISSUES }, + ]); + + fetchManifestSpy.mockRestore(); + }); + + it("dispatches a START - FAILURE sequence when fetching fails", async () => { + const fetchManifestSpy = jest + .spyOn(services, "fetchManifest") + .mockRejectedValue(new Error("lorem ipsum")); + + const store = setupStore({}); + await store.dispatch(fetchManifest()); + + expect(store.getActions()).toEqual([ + { type: FETCH_MANIFEST_START }, + { type: FETCH_MANIFEST_FAILURE, error: "lorem ipsum" }, + ]); + + fetchManifestSpy.mockRestore(); + }); + + it("dispatches a START - FAILURE sequence when fetching fails due to a devtools error", async () => { + const error = new ManifestDevToolsError(":("); + const fetchManifestSpy = jest + .spyOn(services, "fetchManifest") + .mockRejectedValue(error); + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + const store = setupStore({}); + await store.dispatch(fetchManifest()); + + expect(store.getActions()).toEqual([ + { type: FETCH_MANIFEST_START }, + { type: FETCH_MANIFEST_FAILURE, error: "manifest-loaded-devtools-error" }, + ]); + expect(consoleErrorSpy).toHaveBeenCalledWith(error); + + fetchManifestSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + }); +}); diff --git a/devtools/client/application/test/node/babel.config.js b/devtools/client/application/test/node/babel.config.js new file mode 100644 index 0000000000..a9e47ba7fb --- /dev/null +++ b/devtools/client/application/test/node/babel.config.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +module.exports = { + plugins: ["@babel/plugin-proposal-async-generator-functions"], +}; diff --git a/devtools/client/application/test/node/components/__snapshots__/components_application_panel-App.test.js.snap b/devtools/client/application/test/node/components/__snapshots__/components_application_panel-App.test.js.snap new file mode 100644 index 0000000000..cef0e0a85d --- /dev/null +++ b/devtools/client/application/test/node/components/__snapshots__/components_application_panel-App.test.js.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`App renders the expected snapshot 1`] = ` +<LocalizationProvider> + <main + className="app" + > + <Connect(Sidebar) /> + <Connect(PageSwitcher) /> + </main> +</LocalizationProvider> +`; diff --git a/devtools/client/application/test/node/components/components_application_panel-App.test.js b/devtools/client/application/test/node/components/components_application_panel-App.test.js new file mode 100644 index 0000000000..fb003be26e --- /dev/null +++ b/devtools/client/application/test/node/components/components_application_panel-App.test.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +// Import & init localization +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const LocalizationProvider = createFactory(FluentReact.LocalizationProvider); + +// Import component +const App = createFactory( + require("resource://devtools/client/application/src/components/App.js") +); + +describe("App", () => { + it("renders the expected snapshot", () => { + const wrapper = shallow( + LocalizationProvider({ bundles: [] }, App({})) + ).dive(); // dive to bypass the LocalizationProvider wrapper + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap new file mode 100644 index 0000000000..d46eb63334 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap @@ -0,0 +1,396 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Manifest does not render the issues section when the manifest is valid 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody> + <ManifestItem + key="name" + label="name" + > + foo + </ManifestItem> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody> + <ManifestItem + key="lorem" + label="lorem" + > + ipsum + </ManifestItem> + <ManifestItem + key="foo" + label="foo" + > + bar + </ManifestItem> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody /> + </table> + </ManifestSection> +</article> +`; + +exports[`Manifest does render the issues section when the manifest is not valid 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-0" + title="manifest-item-warnings" + > + <ManifestIssueList + issues={ + Array [ + Object { + "level": "warning", + "message": "This is a warning", + }, + ] + } + /> + </ManifestSection> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody> + <ManifestItem + key="name" + label="name" + > + foo + </ManifestItem> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody> + <ManifestItem + key="lorem" + label="lorem" + > + ipsum + </ManifestItem> + <ManifestItem + key="foo" + label="foo" + > + bar + </ManifestItem> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody /> + </table> + </ManifestSection> +</article> +`; + +exports[`Manifest renders the expected snapshot for a manifest with color members 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody /> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody> + <ManifestColorItem + key="background_color" + label="background_color" + value="red" + /> + <ManifestColorItem + key="theme_color" + label="theme_color" + value="rgb(0, 0, 0)" + /> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody /> + </table> + </ManifestSection> +</article> +`; + +exports[`Manifest renders the expected snapshot for a manifest with icon members 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody /> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody /> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody> + <ManifestIconItem + key="0" + label={ + Object { + "contentType": "image/png", + "sizes": "1x1", + } + } + value={ + Object { + "purpose": "any", + "src": "something.png", + } + } + /> + <ManifestIconItem + key="1" + label={ + Object { + "contentType": "", + "sizes": "", + } + } + value={ + Object { + "purpose": "any maskable", + "src": "something.svg", + } + } + /> + </tbody> + </table> + </ManifestSection> +</article> +`; + +exports[`Manifest renders the expected snapshot for a manifest with string members 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody> + <ManifestItem + key="name" + label="name" + > + foo + </ManifestItem> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody /> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody /> + </table> + </ManifestSection> +</article> +`; + +exports[`Manifest renders the expected snapshot for a manifest with unknown types 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody> + <ManifestItem + key="lorem" + label="lorem" + > + ipsum + </ManifestItem> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody /> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody /> + </table> + </ManifestSection> +</article> +`; + +exports[`Manifest renders the expected snapshot for a manifest with url members 1`] = ` +<article + className="js-manifest" +> + <Localized + id="manifest-view-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ManifestJsonLink /> + <ManifestSection + key="manifest-section-1" + title="manifest-item-identity" + > + <table> + <tbody /> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-2" + title="manifest-item-presentation" + > + <table> + <tbody> + <ManifestUrlItem + key="start_url" + label="start_url" + value="https://example.com/" + /> + <ManifestUrlItem + key="scope" + label="scope" + value="https://example.com/" + /> + </tbody> + </table> + </ManifestSection> + <ManifestSection + key="manifest-section-3" + title="manifest-item-icons" + > + <table> + <tbody /> + </table> + </ManifestSection> +</article> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap new file mode 100644 index 0000000000..4f3e485084 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestColorItem does not strip translucent alpha from the displayed color 1`] = ` +<ManifestItem + label="foo" +> + <div + className="manifest-item__color" + style={ + Object { + "--color-value": "#00FF00FA", + } + } + > + #00FF00FA + </div> +</ManifestItem> +`; + +exports[`ManifestColorItem renders the expected snapshot for a populated color item 1`] = ` +<ManifestItem + label="foo" +> + <div + className="manifest-item__color" + style={ + Object { + "--color-value": "#ff0000", + } + } + > + #ff0000 + </div> +</ManifestItem> +`; + +exports[`ManifestColorItem renders the expected snapshot for an empty color item 1`] = ` +<ManifestItem + label="foo" +/> +`; + +exports[`ManifestColorItem strips opaque alpha from the displayed color 1`] = ` +<ManifestItem + label="foo" +> + <div + className="manifest-item__color" + style={ + Object { + "--color-value": "#00FF00", + } + } + > + #00FF00 + </div> +</ManifestItem> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap new file mode 100644 index 0000000000..bdc5e9ed60 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestEmpty renders the expected snapshot 1`] = ` +<article + className="app-page__icon-container js-manifest-empty" +> + <aside> + <Localized + attrs={ + Object { + "alt": true, + } + } + id="sidebar-item-manifest" + > + <img + className="app-page__icon" + src="chrome://devtools/skin/images/application-manifest.svg" + /> + </Localized> + </aside> + <div> + <Localized + id="manifest-empty-intro2" + > + <h1 + className="app-page__title" + /> + </Localized> + <p> + <Localized + id="manifest-empty-intro-link" + > + <a + onClick={[Function]} + /> + </Localized> + </p> + <Localized + id="manifest-non-existing" + > + <p /> + </Localized> + </div> +</article> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap new file mode 100644 index 0000000000..200c6306de --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap @@ -0,0 +1,106 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestIconItem renders the expected snapshop when a label member is missing 1`] = ` +<ManifestItem + label={ + Array [ + null, + null, + "image/png", + ] + } +> + <Localized + attrs={ + Object { + "alt": true, + } + } + id="manifest-icon-img" + > + <img + className="manifest-item__icon" + src="icon.png" + title="manifest-icon-img-title-no-sizes" + /> + </Localized> + <br /> + <Localized + $purpose="any" + code={<code />} + id="manifest-icon-purpose" + > + <span /> + </Localized> +</ManifestItem> +`; + +exports[`ManifestIconItem renders the expected snapshop when all label members are missing 1`] = ` +<ManifestItem + label={ + Array [ + null, + null, + null, + ] + } +> + <Localized + attrs={ + Object { + "alt": true, + } + } + id="manifest-icon-img" + > + <img + className="manifest-item__icon" + src="icon.png" + title="manifest-icon-img-title-no-sizes" + /> + </Localized> + <br /> + <Localized + $purpose="any" + code={<code />} + id="manifest-icon-purpose" + > + <span /> + </Localized> +</ManifestItem> +`; + +exports[`ManifestIconItem renders the expected snapshot for a fully populated icon item 1`] = ` +<ManifestItem + label={ + Array [ + "128x128", + <br />, + "image/png", + ] + } +> + <Localized + attrs={ + Object { + "alt": true, + } + } + id="manifest-icon-img" + > + <img + className="manifest-item__icon" + src="icon.png" + title="manifest-icon-img-title__{\\"sizes\\":\\"128x128\\"}" + /> + </Localized> + <br /> + <Localized + $purpose="any" + code={<code />} + id="manifest-icon-purpose" + > + <span /> + </Localized> +</ManifestItem> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap new file mode 100644 index 0000000000..99e83af1f2 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestIssue renders the expected snapshot for a warning 1`] = ` +<li + className="js-manifest-issue " +> + <Localized + attrs={ + Object { + "alt": true, + "title": true, + } + } + id="icon-warning" + > + <img + className="manifest-issue__icon manifest-issue__icon--warning" + src="chrome://devtools/skin/images/alert-small.svg" + /> + </Localized> + <span> + Lorem ipsum + </span> +</li> +`; + +exports[`ManifestIssue renders the expected snapshot for an error 1`] = ` +<li + className="js-manifest-issue " +> + <Localized + attrs={ + Object { + "alt": true, + "title": true, + } + } + id="icon-error" + > + <img + className="manifest-issue__icon manifest-issue__icon--error" + src="resource://devtools-shared-images/error-small.svg" + /> + </Localized> + <span> + Lorem ipsum + </span> +</li> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap new file mode 100644 index 0000000000..edbf2d07c9 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestIssueList groups issues by level and shows errors first 1`] = ` +Array [ + <ul + className="manifest-issues js-manifest-issues" + key="issuelist-0" + > + <ManifestIssue + className="manifest-issues__item" + key="issue-0" + level="error" + message="An error" + /> + </ul>, + <ul + className="manifest-issues js-manifest-issues" + key="issuelist-1" + > + <ManifestIssue + className="manifest-issues__item" + key="issue-0" + level="warning" + message="A warning" + /> + <ManifestIssue + className="manifest-issues__item" + key="issue-1" + level="warning" + message="Another warning" + /> + </ul>, +] +`; + +exports[`ManifestIssueList renders nothing for empty issues 1`] = `null`; + +exports[`ManifestIssueList renders the expected snapshot for a populated list 1`] = ` +Array [ + <ul + className="manifest-issues js-manifest-issues" + key="issuelist-0" + > + <ManifestIssue + className="manifest-issues__item" + key="issue-0" + level="error" + message="Foo" + /> + </ul>, + <ul + className="manifest-issues js-manifest-issues" + key="issuelist-1" + > + <ManifestIssue + className="manifest-issues__item" + key="issue-0" + level="warning" + message="Foo" + /> + <ManifestIssue + className="manifest-issues__item" + key="issue-1" + level="warning" + message="Bar" + /> + </ul>, +] +`; + +exports[`ManifestIssueList skips rendering empty level groups 1`] = ` +<ul + className="manifest-issues js-manifest-issues" + key="issuelist-0" +> + <ManifestIssue + className="manifest-issues__item" + key="issue-0" + level="warning" + message="A warning" + /> + <ManifestIssue + className="manifest-issues__item" + key="issue-1" + level="warning" + message="Another warning" + /> +</ul> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap new file mode 100644 index 0000000000..69d983d308 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestItem renders the expected snapshot for a populated item 1`] = ` +<tr + className="manifest-item js-manifest-item" +> + <th + className="manifest-item__label js-manifest-item-label" + scope="row" + > + foo + </th> + <td + className="manifest-item__value js-manifest-item-content" + > + bar + </td> +</tr> +`; + +exports[`ManifestItem renders the expected snapshot for an empty item 1`] = ` +<tr + className="manifest-item js-manifest-item" +> + <th + className="manifest-item__label js-manifest-item-label" + scope="row" + > + foo + </th> + <td + className="manifest-item__value js-manifest-item-content" + /> +</tr> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap new file mode 100644 index 0000000000..061578b846 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestJsonLink renders the expected snapshot when given a data URL 1`] = ` +<p + className="manifest-json-link" +> + <Localized + id="manifest-json-link-data-url" + /> +</p> +`; + +exports[`ManifestJsonLink renders the expected snapshot when given a regular URL 1`] = ` +<p + className="manifest-json-link" +> + <a + className="js-manifest-json-link devtools-ellipsis-text" + href="#" + onClick={[Function]} + title="https://example.com/manifest.json" + > + https://example.com/manifest.json + </a> +</p> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap new file mode 100644 index 0000000000..16c885cf80 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestLoader renders a message when it is loading 1`] = ` +<aside + className="manifest-loader" +> + <Localized + id="manifest-loading" + > + <p + className="manifest-loader__load js-manifest-loading" + /> + </Localized> +</aside> +`; + +exports[`ManifestLoader renders a message when manifest has failed to load 1`] = ` +<aside + className="manifest-loader" +> + <Localized + id="manifest-loaded-error" + key="manifest-error-label" + > + <h1 + className="js-manifest-loaded-error app-page__title" + /> + </Localized> + <p + className="technical-text" + key="manifest-error-message" + > + lorem ipsum + </p> +</aside> +`; + +exports[`ManifestLoader renders a message when manifest has loaded OK 1`] = ` +<aside + className="manifest-loader" +> + <Localized + id="manifest-loaded-ok" + > + <p + className="js-manifest-loaded-ok" + /> + </Localized> +</aside> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap new file mode 100644 index 0000000000..4700ccf935 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestPage renders the expected snapshot when the manifest is loading 1`] = ` +<section + className="app-page js-manifest-page app-page--empty" +> + <Connect(ManifestLoader) /> +</section> +`; + +exports[`ManifestPage renders the expected snapshot when the manifest needs to load 1`] = ` +<section + className="app-page js-manifest-page app-page--empty" +> + <Connect(ManifestLoader) /> +</section> +`; + +exports[`ManifestPage renders the expected snapshot when there is a manifest 1`] = ` +<section + className="app-page js-manifest-page " +> + <Manifest + icons={ + Array [ + Object { + "key": Object { + "contentType": "image/png", + "sizes": "1x1", + }, + "type": "icon", + "value": Object { + "purpose": "any", + "src": "something.png", + }, + }, + ] + } + identity={ + Array [ + Object { + "key": "name", + "type": "string", + "value": "foo", + }, + ] + } + presentation={ + Array [ + Object { + "key": "lorem", + "type": "string", + "value": "ipsum", + }, + Object { + "key": "foo", + "type": "string", + "value": "bar", + }, + ] + } + validation={ + Array [ + Object { + "level": "warning", + "message": "This is a warning", + }, + ] + } + /> +</section> +`; + +exports[`ManifestPage renders the expected snapshot when there is no manifest 1`] = ` +<section + className="app-page js-manifest-page app-page--empty" +> + <ManifestEmpty /> +</section> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap new file mode 100644 index 0000000000..e8ea10867f --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestSection renders the expected snapshot for a populated section 1`] = ` +<section + className="manifest-section " +> + <h2 + className="manifest-section__title" + > + Lorem ipsum + </h2> + <tr> + <td> + foo + </td> + </tr> +</section> +`; + +exports[`ManifestSection renders the expected snapshot for a section with no children 1`] = ` +<section + className="manifest-section manifest-section--empty" +> + <h2 + className="manifest-section__title" + > + Lorem ipsum + </h2> +</section> +`; diff --git a/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestUrlItem.test.js.snap b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestUrlItem.test.js.snap new file mode 100644 index 0000000000..62f4fdfe11 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestUrlItem.test.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManifestUrlItem renders the expected snapshot for a populated url 1`] = ` +<ManifestItem + label="foo" +> + <div + className="manifest-item__url" + /> +</ManifestItem> +`; + +exports[`ManifestUrlItem renders the expected snapshot for an empty url 1`] = ` +<ManifestItem + label="foo" +> + <div + className="manifest-item__url" + /> +</ManifestItem> +`; diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-Manifest.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-Manifest.test.js new file mode 100644 index 0000000000..0b554fbdd2 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-Manifest.test.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const Manifest = createFactory( + require("resource://devtools/client/application/src/components/manifest/Manifest.js") +); + +const { + MANIFEST_COLOR_MEMBERS, + MANIFEST_ICON_MEMBERS, + MANIFEST_STRING_MEMBERS, + MANIFEST_UNKNOWN_TYPE_MEMBERS, + MANIFEST_URL_MEMBERS, + MANIFEST_NO_ISSUES, + MANIFEST_WITH_ISSUES, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +/* + * Test for Manifest component + */ + +describe("Manifest", () => { + it("renders the expected snapshot for a manifest with string members", () => { + const wrapper = shallow(Manifest(MANIFEST_STRING_MEMBERS)); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for a manifest with color members", () => { + const wrapper = shallow(Manifest(MANIFEST_COLOR_MEMBERS)); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for a manifest with unknown types", () => { + const wrapper = shallow(Manifest(MANIFEST_UNKNOWN_TYPE_MEMBERS)); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for a manifest with icon members", () => { + const wrapper = shallow(Manifest(MANIFEST_ICON_MEMBERS)); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for a manifest with url members", () => { + const wrapper = shallow(Manifest(MANIFEST_URL_MEMBERS)); + expect(wrapper).toMatchSnapshot(); + }); + + it("does render the issues section when the manifest is not valid", () => { + const wrapper = shallow(Manifest(MANIFEST_WITH_ISSUES)); + expect(wrapper).toMatchSnapshot(); + + const sections = wrapper.find("ManifestSection"); + expect(sections).toHaveLength(4); + expect(sections.get(0).props.title).toBe("manifest-item-warnings"); + expect(sections.find("ManifestIssueList")).toHaveLength(1); + }); + + it("does not render the issues section when the manifest is valid", () => { + const wrapper = shallow(Manifest(MANIFEST_NO_ISSUES)); + expect(wrapper).toMatchSnapshot(); + + const sections = wrapper.find("ManifestSection"); + expect(sections).toHaveLength(3); + expect(sections.get(0).props.title).not.toBe("manifest-item-warnings"); + expect(sections.find("ManifestIssueList")).toHaveLength(0); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestColorItem.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestColorItem.test.js new file mode 100644 index 0000000000..eac67ec195 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestColorItem.test.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestColorItem = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestColorItem.js") +); + +/* + * Unit tests for the ManifestItem component + */ + +describe("ManifestColorItem", () => { + it("renders the expected snapshot for a populated color item", () => { + const wrapper = shallow( + ManifestColorItem({ label: "foo", value: "#ff0000" }) + ); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for an empty color item", () => { + const wrapper = shallow(ManifestColorItem({ label: "foo" })); + expect(wrapper).toMatchSnapshot(); + }); + + it("strips opaque alpha from the displayed color", () => { + const wrapper = shallow( + ManifestColorItem({ label: "foo", value: "#00FF00FF" }) + ); + expect(wrapper).toMatchSnapshot(); + + expect(wrapper.find(".manifest-item__color").text()).toBe("#00FF00"); + }); + + it("does not strip translucent alpha from the displayed color", () => { + const wrapper = shallow( + ManifestColorItem({ label: "foo", value: "#00FF00FA" }) + ); + expect(wrapper).toMatchSnapshot(); + + expect(wrapper.find(".manifest-item__color").text()).toBe("#00FF00FA"); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestEmpty.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestEmpty.test.js new file mode 100644 index 0000000000..de27a56ec5 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestEmpty.test.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestEmpty = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestEmpty.js") +); + +/** + * Test for ManifestEmpty component + */ + +describe("ManifestEmpty", () => { + it("renders the expected snapshot", () => { + const wrapper = shallow(ManifestEmpty({})); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIconItem.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIconItem.test.js new file mode 100644 index 0000000000..e3ea293c82 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIconItem.test.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestIconItem = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestIconItem.js") +); + +/* + * Unit tests for the ManifestIconItem component + */ + +describe("ManifestIconItem", () => { + it("renders the expected snapshot for a fully populated icon item", () => { + const wrapper = shallow( + ManifestIconItem({ + label: { sizes: "128x128", contentType: "image/png" }, + value: { src: "icon.png", purpose: "any" }, + }) + ); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshop when a label member is missing", () => { + const wrapper = shallow( + ManifestIconItem({ + label: { sizes: undefined, contentType: "image/png" }, + value: { src: "icon.png", purpose: "any" }, + }) + ); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshop when all label members are missing", () => { + const wrapper = shallow( + ManifestIconItem({ + label: { sizes: undefined, contentType: undefined }, + value: { src: "icon.png", purpose: "any" }, + }) + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssue.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssue.test.js new file mode 100644 index 0000000000..41350557be --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssue.test.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestIssue = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestIssue.js") +); + +/* + * Tests for the ManifestIssue component + */ + +describe("ManifestIssue", () => { + it("renders the expected snapshot for a warning", () => { + const issue = { level: "warning", message: "Lorem ipsum" }; + const wrapper = shallow(ManifestIssue(issue)); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for an error", () => { + const issue = { level: "error", message: "Lorem ipsum" }; + const wrapper = shallow(ManifestIssue(issue)); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssueList.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssueList.test.js new file mode 100644 index 0000000000..71d59d3e07 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssueList.test.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestIssueList = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestIssueList.js") +); + +/* + * Tests for the ManifestIssue component + */ + +describe("ManifestIssueList", () => { + it("renders the expected snapshot for a populated list", () => { + const issues = [ + { level: "error", message: "Foo" }, + { level: "warning", message: "Foo" }, + { level: "warning", message: "Bar" }, + ]; + const wrapper = shallow(ManifestIssueList({ issues })); + expect(wrapper).toMatchSnapshot(); + }); + + it("groups issues by level and shows errors first", () => { + const issues = [ + { level: "warning", message: "A warning" }, + { level: "error", message: "An error" }, + { level: "warning", message: "Another warning" }, + ]; + const wrapper = shallow(ManifestIssueList({ issues })); + expect(wrapper).toMatchSnapshot(); + + expect(wrapper.find("ManifestIssue").get(0).props.level).toBe("error"); + expect(wrapper.find("ManifestIssue").get(1).props.level).toBe("warning"); + expect(wrapper.find("ManifestIssue").get(2).props.level).toBe("warning"); + }); + + it("skips rendering empty level groups", () => { + const issues = [ + { level: "warning", message: "A warning" }, + { level: "warning", message: "Another warning" }, + ]; + const wrapper = shallow(ManifestIssueList({ issues })); + expect(wrapper).toMatchSnapshot(); + + const lists = wrapper.find(".js-manifest-issues"); + expect(lists).toHaveLength(1); + }); + + it("renders nothing for empty issues", () => { + const wrapper = shallow(ManifestIssueList({ issues: [] })); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestItem.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestItem.test.js new file mode 100644 index 0000000000..ca9303aab4 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestItem.test.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestItem = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestItem.js") +); + +/* + * Unit tests for the ManifestItem component + */ + +describe("ManifestItem", () => { + it("renders the expected snapshot for a populated item", () => { + const wrapper = shallow(ManifestItem({ label: "foo" }, "bar")); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for an empty item", () => { + const wrapper = shallow(ManifestItem({ label: "foo" })); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestJsonLink.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestJsonLink.test.js new file mode 100644 index 0000000000..fccab31b9d --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestJsonLink.test.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestJsonLink = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestJsonLink.js") +); + +/* + * Test for the ManifestJsonLink component + */ + +describe("ManifestJsonLink", () => { + it("renders the expected snapshot when given a regular URL", () => { + const wrapper = shallow( + ManifestJsonLink({ url: "https://example.com/manifest.json" }) + ); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when given a data URL", () => { + const wrapper = shallow( + ManifestJsonLink({ + url: `data:application/manifest+json,{"name": "Foo"}`, + }) + ); + expect(wrapper).toMatchSnapshot(); + // assert there's no link for data URLs + expect(wrapper.find("a").length).toBe(0); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestLoader.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestLoader.test.js new file mode 100644 index 0000000000..d3cc8595ce --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestLoader.test.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); +// Import test helpers +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); +// Import fixtures +const { + MANIFEST_NO_ISSUES, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +const manifestActions = require("resource://devtools/client/application/src/actions/manifest.js"); +// NOTE: we need to spy on the action before we load the component, so it gets +// bound to the spy, not the original implementation +const fetchManifestActionSpy = jest.spyOn(manifestActions, "fetchManifest"); + +const ManifestLoader = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestLoader.js") +); + +describe("ManifestLoader", () => { + function buildStore({ manifest, errorMessage, isLoading }) { + const manifestState = Object.assign( + { + manifest: null, + errorMessage: "", + isLoading: false, + }, + { manifest, errorMessage, isLoading } + ); + + return setupStore({ manifest: manifestState }); + } + + afterAll(() => { + fetchManifestActionSpy.mockRestore(); + }); + + it("loads a manifest when mounted", async () => { + fetchManifestActionSpy.mockReturnValue({ type: "foo" }); + + const store = buildStore({}); + + shallow(ManifestLoader({ store })).dive(); + + expect(manifestActions.fetchManifest).toHaveBeenCalled(); + fetchManifestActionSpy.mockReset(); + }); + + it("renders a message when it is loading", async () => { + const store = buildStore({ isLoading: true }); + const wrapper = shallow(ManifestLoader({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders a message when manifest has loaded OK", async () => { + const store = buildStore({ + isLoading: false, + manifest: MANIFEST_NO_ISSUES, + errorMessage: "", + }); + const wrapper = shallow(ManifestLoader({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders a message when manifest has failed to load", async () => { + const store = buildStore({ + manifest: null, + isLoading: false, + errorMessage: "lorem ipsum", + }); + const wrapper = shallow(ManifestLoader({ store })).dive(); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestPage.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestPage.test.js new file mode 100644 index 0000000000..b479882924 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestPage.test.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); +const { + MANIFEST_SIMPLE, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +const ManifestPage = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestPage.js") +); + +/** + * Test for ManifestPage.js component + */ + +describe("ManifestPage", () => { + function buildStoreWithManifest(manifest, isLoading = false) { + return setupStore({ + manifest: { + manifest, + errorMessage: "", + isLoading, + }, + }); + } + + it("renders the expected snapshot when there is a manifest", () => { + const store = buildStoreWithManifest(MANIFEST_SIMPLE); + const wrapper = shallow(ManifestPage({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when the manifest needs to load", () => { + const store = buildStoreWithManifest(undefined); + const wrapper = shallow(ManifestPage({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when the manifest is loading", () => { + const store = buildStoreWithManifest(undefined, true); + const wrapper = shallow(ManifestPage({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when there is no manifest", () => { + const store = buildStoreWithManifest(null); + const wrapper = shallow(ManifestPage({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestSection.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestSection.test.js new file mode 100644 index 0000000000..c117febb57 --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestSection.test.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); +const { + td, + tr, +} = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +const ManifestSection = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestSection.js") +); + +/* + * Unit tests for the ManifestSection component + */ + +describe("ManifestSection", () => { + it("renders the expected snapshot for a populated section", () => { + const content = tr({}, td({}, "foo")); + const wrapper = shallow(ManifestSection({ title: "Lorem ipsum" }, content)); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for a section with no children", () => { + const wrapper = shallow(ManifestSection({ title: "Lorem ipsum" })); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find(".manifest-section--empty")); + }); +}); diff --git a/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestUrlItem.test.js b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestUrlItem.test.js new file mode 100644 index 0000000000..1ef1504a3f --- /dev/null +++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestUrlItem.test.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const ManifestUrlItem = createFactory( + require("resource://devtools/client/application/src/components/manifest/ManifestUrlItem.js") +); + +/* + * Unit tests for the ManifestUrlItem component + */ + +describe("ManifestUrlItem", () => { + it("renders the expected snapshot for a populated url", () => { + const wrapper = shallow( + ManifestUrlItem({ label: "foo" }, "https://example.com") + ); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for an empty url", () => { + const wrapper = shallow(ManifestUrlItem({ label: "foo" })); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap b/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap new file mode 100644 index 0000000000..4fc899a511 --- /dev/null +++ b/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PageSwitcher renders nothing when an invalid page is selected 1`] = `""`; + +exports[`PageSwitcher renders nothing when no page is selected 1`] = `""`; + +exports[`PageSwitcher renders the ManifestPage component when manifest page is selected 1`] = `<Connect(ManifestPage) />`; + +exports[`PageSwitcher renders the WorkersPage component when workers page is selected 1`] = `<Connect(WorkersPage) />`; diff --git a/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap b/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap new file mode 100644 index 0000000000..4348d7c5b9 --- /dev/null +++ b/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Sidebar renders the expected snapshot when no page is selected 1`] = ` +<aside + className="sidebar js-sidebar" +> + <ul + className="sidebar__list" + > + <Connect(SidebarItem) + isSelected={false} + key="sidebar-item-service-workers" + page="service-workers" + /> + <Connect(SidebarItem) + isSelected={false} + key="sidebar-item-manifest" + page="manifest" + /> + </ul> +</aside> +`; + +exports[`Sidebar renders the expected snapshot when the manifest page is selected 1`] = ` +<aside + className="sidebar js-sidebar" +> + <ul + className="sidebar__list" + > + <Connect(SidebarItem) + isSelected={false} + key="sidebar-item-service-workers" + page="service-workers" + /> + <Connect(SidebarItem) + isSelected={true} + key="sidebar-item-manifest" + page="manifest" + /> + </ul> +</aside> +`; + +exports[`Sidebar renders the expected snapshot when the service workers page is selected 1`] = ` +<aside + className="sidebar js-sidebar" +> + <ul + className="sidebar__list" + > + <Connect(SidebarItem) + isSelected={true} + key="sidebar-item-service-workers" + page="service-workers" + /> + <Connect(SidebarItem) + isSelected={false} + key="sidebar-item-manifest" + page="manifest" + /> + </ul> +</aside> +`; diff --git a/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap b/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap new file mode 100644 index 0000000000..c5f3122e6c --- /dev/null +++ b/devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap @@ -0,0 +1,141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SidebarItem renders the expected snapshot when the manifest page is not selected 1`] = ` +<li + className="sidebar-item js-sidebar-manifest " + onClick={[Function]} + role="link" +> + <Localized + attrs={ + Object { + "alt": true, + "title": true, + } + } + id="sidebar-item-manifest" + > + <img + className="sidebar-item__icon" + src="chrome://devtools/skin/images/application-manifest.svg" + /> + </Localized> + <Localized + attrs={ + Object { + "title": true, + } + } + id="sidebar-item-manifest" + > + <span + className="devtools-ellipsis-text" + /> + </Localized> +</li> +`; + +exports[`SidebarItem renders the expected snapshot when the manifest page is selected 1`] = ` +<li + className="sidebar-item js-sidebar-manifest sidebar-item--selected" + onClick={[Function]} + role="link" +> + <Localized + attrs={ + Object { + "alt": true, + "title": true, + } + } + id="sidebar-item-manifest" + > + <img + className="sidebar-item__icon" + src="chrome://devtools/skin/images/application-manifest.svg" + /> + </Localized> + <Localized + attrs={ + Object { + "title": true, + } + } + id="sidebar-item-manifest" + > + <span + className="devtools-ellipsis-text" + /> + </Localized> +</li> +`; + +exports[`SidebarItem renders the expected snapshot when the service-workers page is not selected 1`] = ` +<li + className="sidebar-item js-sidebar-service-workers " + onClick={[Function]} + role="link" +> + <Localized + attrs={ + Object { + "alt": true, + "title": true, + } + } + id="sidebar-item-service-workers" + > + <img + className="sidebar-item__icon" + src="chrome://devtools/skin/images/debugging-workers.svg" + /> + </Localized> + <Localized + attrs={ + Object { + "title": true, + } + } + id="sidebar-item-service-workers" + > + <span + className="devtools-ellipsis-text" + /> + </Localized> +</li> +`; + +exports[`SidebarItem renders the expected snapshot when the service-workers page is selected 1`] = ` +<li + className="sidebar-item js-sidebar-service-workers sidebar-item--selected" + onClick={[Function]} + role="link" +> + <Localized + attrs={ + Object { + "alt": true, + "title": true, + } + } + id="sidebar-item-service-workers" + > + <img + className="sidebar-item__icon" + src="chrome://devtools/skin/images/debugging-workers.svg" + /> + </Localized> + <Localized + attrs={ + Object { + "title": true, + } + } + id="sidebar-item-service-workers" + > + <span + className="devtools-ellipsis-text" + /> + </Localized> +</li> +`; diff --git a/devtools/client/application/test/node/components/routing/components_application_panel-PageSwitcher.test.js b/devtools/client/application/test/node/components/routing/components_application_panel-PageSwitcher.test.js new file mode 100644 index 0000000000..1e69aa6869 --- /dev/null +++ b/devtools/client/application/test/node/components/routing/components_application_panel-PageSwitcher.test.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +// Import setupStore with imported & combined reducers +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +const PageSwitcher = createFactory( + require("resource://devtools/client/application/src/components/routing/PageSwitcher.js") +); + +const { + PAGE_TYPES, +} = require("resource://devtools/client/application/src/constants.js"); + +/** + * Test for workerListEmpty.js component + */ + +describe("PageSwitcher", () => { + function buildStoreWithSelectedPage(selectedPage) { + return setupStore({ + ui: { + selectedPage, + }, + }); + } + + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + beforeEach(() => { + console.error.mockClear(); + }); + + afterAll(() => { + consoleErrorSpy.mockRestore(); + }); + + it("renders the ManifestPage component when manifest page is selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.MANIFEST); + const wrapper = shallow(PageSwitcher({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the WorkersPage component when workers page is selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.SERVICE_WORKERS); + const wrapper = shallow(PageSwitcher({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders nothing when no page is selected", () => { + const store = buildStoreWithSelectedPage(null); + const wrapper = shallow(PageSwitcher({ store })).dive(); + expect(console.error).toHaveBeenCalledTimes(1); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders nothing when an invalid page is selected", () => { + const store = buildStoreWithSelectedPage("foo"); + const wrapper = shallow(PageSwitcher({ store })).dive(); + expect(console.error).toHaveBeenCalledTimes(1); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/routing/components_application_panel-Sidebar.test.js b/devtools/client/application/test/node/components/routing/components_application_panel-Sidebar.test.js new file mode 100644 index 0000000000..4593f702bc --- /dev/null +++ b/devtools/client/application/test/node/components/routing/components_application_panel-Sidebar.test.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +const { + PAGE_TYPES, +} = require("resource://devtools/client/application/src/constants.js"); + +const Sidebar = createFactory( + require("resource://devtools/client/application/src/components/routing/Sidebar.js") +); + +/** + * Test for Sidebar.js component + */ + +describe("Sidebar", () => { + function buildStoreWithSelectedPage(selectedPage) { + return setupStore({ + ui: { + selectedPage, + }, + }); + } + it("renders the expected snapshot when the manifest page is selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.MANIFEST); + const wrapper = shallow(Sidebar({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when the service workers page is selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.SERVICE_WORKERS); + const wrapper = shallow(Sidebar({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when no page is selected", () => { + const store = buildStoreWithSelectedPage(); + const wrapper = shallow(Sidebar({ store })).dive(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/routing/components_application_panel-SidebarItem.test.js b/devtools/client/application/test/node/components/routing/components_application_panel-SidebarItem.test.js new file mode 100644 index 0000000000..955c97acd2 --- /dev/null +++ b/devtools/client/application/test/node/components/routing/components_application_panel-SidebarItem.test.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +const { + PAGE_TYPES, +} = require("resource://devtools/client/application/src/constants.js"); + +const SidebarItem = createFactory( + require("resource://devtools/client/application/src/components/routing/SidebarItem.js") +); + +/** + * Test for SidebarItem.js component + */ + +describe("SidebarItem", () => { + function buildStoreWithSelectedPage(selectedPage) { + return setupStore({ + ui: { + selectedPage, + }, + }); + } + + it("renders the expected snapshot when the manifest page is selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.MANIFEST); + const wrapper = shallow( + SidebarItem({ + store, + page: "manifest", + isSelected: true, + }) + ).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when the service-workers page is selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.SERVICE_WORKERS); + const wrapper = shallow( + SidebarItem({ + store, + isSelected: true, + page: "service-workers", + }) + ).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when the manifest page is not selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.MANIFEST); + const wrapper = shallow( + SidebarItem({ + store, + isSelected: false, + page: "manifest", + }) + ).dive(); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot when the service-workers page is not selected", () => { + const store = buildStoreWithSelectedPage(PAGE_TYPES.SERVICE_WORKERS); + const wrapper = shallow( + SidebarItem({ + store, + isSelected: false, + page: "service-workers", + }) + ).dive(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Registration.test.js.snap b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Registration.test.js.snap new file mode 100644 index 0000000000..f85bf8b2a1 --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Registration.test.js.snap @@ -0,0 +1,180 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Registration Renders the expected snapshot for a registration with a worker 1`] = ` +<li + className="" +> + <article + className="registration js-sw-container" + > + <header + className="registration__header" + > + <h2 + className="registration__scope js-sw-scope devtools-ellipsis-text" + title="SCOPE 123" + > + SCOPE 123 + </h2> + </header> + <aside + className="registration__controls" + > + <Localized + id="serviceworker-worker-unregister" + > + <UIButton + className="js-unregister-button" + onClick={[Function]} + /> + </Localized> + </aside> + <ul + className="registration__workers" + > + <li + className="registration__workers-item" + key="id-worker-1-example" + > + <Connect(Worker) + isDebugEnabled={true} + worker={ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + } + } + /> + </li> + </ul> + </article> +</li> +`; + +exports[`Registration Renders the expected snapshot for a registration with multiple workers 1`] = ` +<li + className="" +> + <article + className="registration js-sw-container" + > + <header + className="registration__header" + > + <h2 + className="registration__scope js-sw-scope devtools-ellipsis-text" + title="SCOPE 123" + > + SCOPE 123 + </h2> + </header> + <aside + className="registration__controls" + > + <Localized + id="serviceworker-worker-unregister" + > + <UIButton + className="js-unregister-button" + onClick={[Function]} + /> + </Localized> + </aside> + <ul + className="registration__workers" + > + <li + className="registration__workers-item" + key="id-worker-1-example" + > + <Connect(Worker) + isDebugEnabled={true} + worker={ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + } + } + /> + </li> + <li + className="registration__workers-item" + key="id-worker-2-example" + > + <Connect(Worker) + isDebugEnabled={true} + worker={ + Object { + "id": "id-worker-2-example", + "state": 2, + "stateText": "installed", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + } + } + /> + </li> + </ul> + </article> +</li> +`; + +exports[`Registration Renders the expected snapshot when sw debugging is disabled 1`] = ` +<li + className="" +> + <article + className="registration js-sw-container" + > + <header + className="registration__header" + > + <h2 + className="registration__scope js-sw-scope devtools-ellipsis-text" + title="SCOPE 123" + > + SCOPE 123 + </h2> + </header> + <aside + className="registration__controls" + > + <Localized + id="serviceworker-worker-unregister" + > + <UIButton + className="js-unregister-button" + onClick={[Function]} + /> + </Localized> + </aside> + <ul + className="registration__workers" + > + <li + className="registration__workers-item" + key="id-worker-1-example" + > + <Connect(Worker) + isDebugEnabled={false} + worker={ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + } + } + /> + </li> + </ul> + </article> +</li> +`; diff --git a/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationList.test.js.snap b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationList.test.js.snap new file mode 100644 index 0000000000..160bff7c8c --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationList.test.js.snap @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RegistrationList renders the expected snapshot for a list with a single registration 1`] = ` +Array [ + <article + className="registrations-container" + key="registrations-container" + > + <Localized + id="serviceworker-list-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ul + className="registrations-container__list" + > + <Connect(Registration) + className="registrations-container__item" + isDebugEnabled={true} + key="id-reg-1-example" + registration={ + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE 123", + "workers": Array [ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + } + } + /> + </ul> + </article>, + <footer + className="aboutdebugging-plug" + > + <Localized + a={ + <a + className="aboutdebugging-plug__link" + onClick={[Function]} + /> + } + id="serviceworker-list-aboutdebugging" + key="serviceworkerlist-footer" + > + <p /> + </Localized> + </footer>, +] +`; + +exports[`RegistrationList renders the expected snapshot for a multiple registration list 1`] = ` +Array [ + <article + className="registrations-container" + key="registrations-container" + > + <Localized + id="serviceworker-list-header" + > + <h1 + className="app-page__title" + /> + </Localized> + <ul + className="registrations-container__list" + > + <Connect(Registration) + className="registrations-container__item" + isDebugEnabled={true} + key="id-reg-1-example" + registration={ + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE1", + "workers": Array [ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + } + } + /> + <Connect(Registration) + className="registrations-container__item" + isDebugEnabled={true} + key="id-reg-1-example" + registration={ + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE2", + "workers": Array [ + Object { + "id": "id-worker-2-example", + "state": 2, + "stateText": "installed", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + } + } + /> + <Connect(Registration) + className="registrations-container__item" + isDebugEnabled={true} + key="id-reg-3-example" + registration={ + Object { + "id": "id-reg-3-example", + "registrationFront": "", + "scope": "SCOPE3", + "workers": Array [ + Object { + "id": "id-worker-3-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + } + } + /> + </ul> + </article>, + <footer + className="aboutdebugging-plug" + > + <Localized + a={ + <a + className="aboutdebugging-plug__link" + onClick={[Function]} + /> + } + id="serviceworker-list-aboutdebugging" + key="serviceworkerlist-footer" + > + <p /> + </Localized> + </footer>, +] +`; diff --git a/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationListEmpty.test.js.snap b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationListEmpty.test.js.snap new file mode 100644 index 0000000000..657c7164d7 --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationListEmpty.test.js.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RegistrationListEmpty renders the expected snapshot 1`] = ` +<article + className="app-page__icon-container js-registration-list-empty" +> + <aside> + <Localized + attrs={ + Object { + "alt": true, + } + } + id="sidebar-item-service-workers" + > + <img + className="app-page__icon" + src="chrome://devtools/skin/images/debugging-workers.svg" + /> + </Localized> + </aside> + <div> + <Localized + id="serviceworker-empty-intro2" + > + <h1 + className="app-page__title" + /> + </Localized> + <Localized + a={ + <a + onClick={[Function]} + /> + } + id="serviceworker-empty-suggestions2" + span={ + <a + onClick={[Function]} + /> + } + > + <p /> + </Localized> + <p> + <Localized + id="serviceworker-empty-intro-link" + > + <a + onClick={[Function]} + /> + </Localized> + </p> + <p> + <Localized + id="serviceworker-empty-suggestions-aboutdebugging2" + > + <a + className="js-trusted-link" + onClick={[Function]} + /> + </Localized> + </p> + </div> +</article> +`; diff --git a/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Worker.test.js.snap b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Worker.test.js.snap new file mode 100644 index 0000000000..7e71765e90 --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Worker.test.js.snap @@ -0,0 +1,132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Worker Renders the expected snapshot for a non-active worker 1`] = ` +<section + className="worker js-sw-worker" +> + <p + className="worker__icon" + > + <img + className="worker__icon-image" + src="chrome://devtools/skin/images/debugging-workers.svg" + /> + </p> + <p + className="worker__source" + > + <span + className="js-source-url" + > + worker.js + </span> + </p> + <p + className="worker__misc" + > + <span + className="js-worker-status worker__status worker__status--waiting" + > + installed + </span> + + </p> +</section> +`; + +exports[`Worker Renders the expected snapshot for a running worker 1`] = ` +<section + className="worker js-sw-worker" +> + <p + className="worker__icon" + > + <img + className="worker__icon-image" + src="chrome://devtools/skin/images/debugging-workers.svg" + /> + </p> + <p + className="worker__source" + > + <a + className="js-inspect-link" + href="#" + onClick={[Function]} + title="http://example.com/worker.js" + > + <span + className="js-source-url" + > + worker.js + </span> +  + <Localized + attrs={ + Object { + "alt": true, + } + } + id="serviceworker-worker-inspect-icon" + > + <img + src="chrome://devtools/skin/images/application-debug.svg" + /> + </Localized> + </a> + </p> + <p + className="worker__misc" + > + <span + className="js-worker-status worker__status worker__status--active" + > + serviceworker-worker-status-running + </span> + + </p> +</section> +`; + +exports[`Worker Renders the expected snapshot for a stopped worker 1`] = ` +<section + className="worker js-sw-worker" +> + <p + className="worker__icon" + > + <img + className="worker__icon-image" + src="chrome://devtools/skin/images/debugging-workers.svg" + /> + </p> + <p + className="worker__source" + > + <span + className="js-source-url" + > + worker.js + </span> + </p> + <p + className="worker__misc" + > + <span + className="js-worker-status worker__status worker__status--active" + > + serviceworker-worker-status-stopped + </span> + + <Localized + id="serviceworker-worker-start3" + > + <UIButton + className="js-start-button" + onClick={[Function]} + size="micro" + /> + </Localized> + </p> +</section> +`; diff --git a/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap new file mode 100644 index 0000000000..5de3e33b4e --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`WorkersPage filters out workers from diferent domains 1`] = ` +<section + className="app-page js-service-workers-page " +> + <RegistrationList + canDebugWorkers={true} + registrations={ + Array [ + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE1", + "workers": Array [ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + }, + Object { + "id": "id-reg-2-example", + "registrationFront": "", + "scope": "SCOPE2", + "workers": Array [ + Object { + "id": "id-worker-2-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + }, + ] + } + /> +</section> +`; + +exports[`WorkersPage filters out workers from different domains and renders an empty list when there is none left 1`] = ` +<section + className="app-page js-service-workers-page app-page--empty" +> + <RegistrationListEmpty /> +</section> +`; + +exports[`WorkersPage it renders a list with a single element if there's just 1 worker 1`] = ` +<section + className="app-page js-service-workers-page " +> + <RegistrationList + canDebugWorkers={true} + registrations={ + Array [ + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE 123", + "workers": Array [ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + }, + ] + } + /> +</section> +`; + +exports[`WorkersPage renders a list with multiple elements when there are multiple workers 1`] = ` +<section + className="app-page js-service-workers-page " +> + <RegistrationList + canDebugWorkers={true} + registrations={ + Array [ + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE1", + "workers": Array [ + Object { + "id": "id-worker-1-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + }, + Object { + "id": "id-reg-1-example", + "registrationFront": "", + "scope": "SCOPE2", + "workers": Array [ + Object { + "id": "id-worker-2-example", + "state": 2, + "stateText": "installed", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + }, + Object { + "id": "id-reg-3-example", + "registrationFront": "", + "scope": "SCOPE3", + "workers": Array [ + Object { + "id": "id-worker-3-example", + "state": 4, + "stateText": "activated", + "url": "http://example.com/worker.js", + "workerDescriptorFront": "", + }, + ], + }, + ] + } + /> +</section> +`; + +exports[`WorkersPage renders an empty list if there are no workers 1`] = ` +<section + className="app-page js-service-workers-page app-page--empty" +> + <RegistrationListEmpty /> +</section> +`; diff --git a/devtools/client/application/test/node/components/service-workers/components_application_panel-Registration.test.js b/devtools/client/application/test/node/components/service-workers/components_application_panel-Registration.test.js new file mode 100644 index 0000000000..7473a61d23 --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-Registration.test.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); +// Import test helpers +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +const { + REGISTRATION_SINGLE_WORKER, + REGISTRATION_MULTIPLE_WORKERS, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +const Registration = createFactory( + require("resource://devtools/client/application/src/components/service-workers/Registration.js") +); + +describe("Registration", () => { + it("Renders the expected snapshot for a registration with a worker", () => { + const store = setupStore({}); + + const wrapper = shallow( + Registration({ + isDebugEnabled: true, + registration: REGISTRATION_SINGLE_WORKER, + store, + }) + ).dive(); + + expect(wrapper).toMatchSnapshot(); + // ensure that we do have the proper amount of workers + expect(wrapper.find("Connect(Worker)")).toHaveLength(1); + }); + + it("Renders the expected snapshot for a registration with multiple workers", () => { + const store = setupStore({}); + + const wrapper = shallow( + Registration({ + isDebugEnabled: true, + registration: REGISTRATION_MULTIPLE_WORKERS, + store, + }) + ).dive(); + + expect(wrapper).toMatchSnapshot(); + // ensure that we do have the proper amount of workers + expect(wrapper.find("Connect(Worker)")).toHaveLength(2); + }); + + it("Renders the expected snapshot when sw debugging is disabled", () => { + const store = setupStore({}); + + const wrapper = shallow( + Registration({ + isDebugEnabled: false, + registration: REGISTRATION_SINGLE_WORKER, + store, + }) + ).dive(); + + expect(wrapper).toMatchSnapshot(); + }); + + it("Removes the ending forward slash from the scope, when present", () => { + const store = setupStore({}); + + const registration = Object.assign({}, REGISTRATION_SINGLE_WORKER, { + scope: "https://example.com/something/", + }); + + const wrapper = shallow( + Registration({ + isDebugEnabled: false, + registration, + store, + }) + ).dive(); + + const scopeEl = wrapper.find(".js-sw-scope"); + expect(scopeEl.text()).toBe("example.com/something"); + }); +}); diff --git a/devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationList.test.js b/devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationList.test.js new file mode 100644 index 0000000000..6bb202cd25 --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationList.test.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +// Import constants +const { + SINGLE_WORKER_DEFAULT_DOMAIN_LIST, + MULTIPLE_WORKER_LIST, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +const RegistrationList = createFactory( + require("resource://devtools/client/application/src/components/service-workers/RegistrationList.js") +); + +/** + * Test for RegistrationList.js component + */ +describe("RegistrationList", () => { + it("renders the expected snapshot for a list with a single registration", () => { + const wrapper = shallow( + RegistrationList({ + registrations: SINGLE_WORKER_DEFAULT_DOMAIN_LIST, + canDebugWorkers: true, + }) + ); + expect(wrapper).toMatchSnapshot(); + }); + + it("renders the expected snapshot for a multiple registration list", () => { + const wrapper = shallow( + RegistrationList({ + registrations: MULTIPLE_WORKER_LIST, + canDebugWorkers: true, + }) + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationListEmpty.test.js b/devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationListEmpty.test.js new file mode 100644 index 0000000000..811535facc --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationListEmpty.test.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +const RegistrationListEmpty = createFactory( + require("resource://devtools/client/application/src/components/service-workers/RegistrationListEmpty.js") +); + +/** + * Test for RegistrationListEmpty.js component + */ + +describe("RegistrationListEmpty", () => { + it("renders the expected snapshot", () => { + const wrapper = shallow(RegistrationListEmpty({})); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/service-workers/components_application_panel-Worker.test.js b/devtools/client/application/test/node/components/service-workers/components_application_panel-Worker.test.js new file mode 100644 index 0000000000..36a6acda0c --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-Worker.test.js @@ -0,0 +1,110 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); +// Import test helpers +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +const { + WORKER_RUNNING, + WORKER_STOPPED, + WORKER_WAITING, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +const Worker = createFactory( + require("resource://devtools/client/application/src/components/service-workers/Worker.js") +); + +describe("Worker", () => { + it("Renders the expected snapshot for a running worker", () => { + const store = setupStore({}); + + const wrapper = shallow( + Worker({ + isDebugEnabled: true, + worker: WORKER_RUNNING, + store, + }) + ).dive(); + + // ensure proper status + expect(wrapper.find(".js-worker-status").text()).toBe( + "serviceworker-worker-status-running" + ); + // check that Start button is not available + expect(wrapper.find(".js-start-button")).toHaveLength(0); + + expect(wrapper).toMatchSnapshot(); + }); + + it("Renders the expected snapshot for a stopped worker", () => { + const store = setupStore({}); + + const wrapper = shallow( + Worker({ + isDebugEnabled: true, + worker: WORKER_STOPPED, + store, + }) + ).dive(); + + // ensure proper status + expect(wrapper.find(".js-worker-status").text()).toBe( + "serviceworker-worker-status-stopped" + ); + // check that Start button is available + expect(wrapper.find(".js-start-button")).toHaveLength(1); + // check that inspect link does not exist + expect(wrapper.find(".js-inspect-link")).toHaveLength(0); + + expect(wrapper).toMatchSnapshot(); + }); + + it("Renders the start button even if debugging workers is disabled", () => { + const store = setupStore({}); + + const wrapper = shallow( + Worker({ + isDebugEnabled: false, + worker: WORKER_STOPPED, + store, + }) + ).dive(); + + // ensure proper status + expect(wrapper.find(".js-worker-status").text()).toBe( + "serviceworker-worker-status-stopped" + ); + // check that Start button is available + expect(wrapper.find(".js-start-button")).toHaveLength(1); + }); + + it("Renders the expected snapshot for a non-active worker", () => { + const store = setupStore({}); + + const wrapper = shallow( + Worker({ + isDebugEnabled: true, + worker: WORKER_WAITING, + store, + }) + ).dive(); + + // ensure proper status + // NOTE: since non-active status are localized directly in the front, not + // in the panel, we don't expect a localization ID here + expect(wrapper.find(".js-worker-status").text()).toBe("installed"); + // check that Start button is not available + expect(wrapper.find(".js-start-button")).toHaveLength(0); + // check that Debug link does not exist + expect(wrapper.find(".js-inspect-link")).toHaveLength(0); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/application/test/node/components/service-workers/components_application_panel-WorkersPage.test.js b/devtools/client/application/test/node/components/service-workers/components_application_panel-WorkersPage.test.js new file mode 100644 index 0000000000..be1b14f216 --- /dev/null +++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-WorkersPage.test.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Import libs +const { shallow } = require("enzyme"); +const { createFactory } = require("react"); + +// Import fixtures +const { + EMPTY_WORKER_LIST, + SINGLE_WORKER_DEFAULT_DOMAIN_LIST, + SINGLE_WORKER_DIFFERENT_DOMAIN_LIST, + MULTIPLE_WORKER_LIST, + MULTIPLE_WORKER_MIXED_DOMAINS_LIST, +} = require("resource://devtools/client/application/test/node/fixtures/data/constants.js"); + +// Import setupStore with imported & combined reducers +const { + setupStore, +} = require("resource://devtools/client/application/test/node/helpers.js"); + +// Import component +const WorkersPage = createFactory( + require("resource://devtools/client/application/src/components/service-workers/WorkersPage.js") +); + +/** + * Test for App.js component + */ +describe("WorkersPage", () => { + const baseState = { + workers: { list: [], canDebugWorkers: true }, + page: { domain: "example.com" }, + }; + + function buildStoreWithWorkers(workerList) { + const workers = { list: workerList, canDebugWorkers: true }; + const state = Object.assign({}, baseState, { workers }); + return setupStore(state); + } + + it("renders an empty list if there are no workers", () => { + const store = buildStoreWithWorkers(EMPTY_WORKER_LIST); + const wrapper = shallow(WorkersPage({ store })).dive(); + + expect(wrapper).toMatchSnapshot(); + }); + + it("it renders a list with a single element if there's just 1 worker", () => { + const store = buildStoreWithWorkers(SINGLE_WORKER_DEFAULT_DOMAIN_LIST); + const wrapper = shallow(WorkersPage({ store })).dive(); + + expect(wrapper).toMatchSnapshot(); + }); + + it("renders a list with multiple elements when there are multiple workers", () => { + const store = buildStoreWithWorkers(MULTIPLE_WORKER_LIST); + const wrapper = shallow(WorkersPage({ store })).dive(); + + expect(wrapper).toMatchSnapshot(); + }); + + it("filters out workers from diferent domains", () => { + const store = buildStoreWithWorkers(MULTIPLE_WORKER_MIXED_DOMAINS_LIST); + const wrapper = shallow(WorkersPage({ store })).dive(); + + expect(wrapper).toMatchSnapshot(); + }); + + it( + "filters out workers from different domains and renders an empty list when " + + "there is none left", + () => { + const store = buildStoreWithWorkers(SINGLE_WORKER_DIFFERENT_DOMAIN_LIST); + const wrapper = shallow(WorkersPage({ store })).dive(); + + expect(wrapper).toMatchSnapshot(); + } + ); +}); diff --git a/devtools/client/application/test/node/fixtures/data/constants.js b/devtools/client/application/test/node/fixtures/data/constants.js new file mode 100644 index 0000000000..795324525c --- /dev/null +++ b/devtools/client/application/test/node/fixtures/data/constants.js @@ -0,0 +1,312 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// NOTE: worker state values are defined in an enum in nsIServiceWorkerManager +// https://searchfox.org/mozilla-central/source/dom/interfaces/base/nsIServiceWorkerManager.idl + +const EMPTY_WORKER_LIST = []; + +const WORKER_RUNNING = { + id: "id-worker-1-example", + workerDescriptorFront: true, + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", +}; + +const WORKER_STOPPED = { + id: "id-worker-1-example", + workerDescriptorFront: false, + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", +}; + +const WORKER_WAITING = { + id: "id-worker-1-example", + workerDescriptorFront: false, + url: "http://example.com/worker.js", + state: 2, + stateText: "installed", +}; + +const REGISTRATION_SINGLE_WORKER = { + id: "id-reg-1-example", + scope: "SCOPE 123", + registrationFront: "", + workers: [ + { + id: "id-worker-1-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], +}; + +const REGISTRATION_MULTIPLE_WORKERS = { + id: "id-reg-1-example", + scope: "SCOPE 123", + registrationFront: "", + workers: [ + { + id: "id-worker-1-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + { + id: "id-worker-2-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 2, + stateText: "installed", + }, + ], +}; + +const SINGLE_WORKER_DEFAULT_DOMAIN_LIST = [ + { + id: "id-reg-1-example", + scope: "SCOPE 123", + registrationFront: "", + workers: [ + { + id: "id-worker-1-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, +]; + +const SINGLE_WORKER_DIFFERENT_DOMAIN_LIST = [ + { + id: "id-reg-1-example", + scope: "SCOPE 123", + registrationFront: "", + workers: [ + { + id: "id-worker-1-example", + workerDescriptorFront: "", + url: "http://different-example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, +]; + +const MULTIPLE_WORKER_LIST = [ + { + id: "id-reg-1-example", + scope: "SCOPE1", + registrationFront: "", + workers: [ + { + id: "id-worker-1-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, + { + id: "id-reg-1-example", + scope: "SCOPE2", + registrationFront: "", + workers: [ + { + id: "id-worker-2-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 2, + stateText: "installed", + }, + ], + }, + { + id: "id-reg-3-example", + scope: "SCOPE3", + registrationFront: "", + workers: [ + { + id: "id-worker-3-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, +]; + +const MULTIPLE_WORKER_MIXED_DOMAINS_LIST = [ + { + id: "id-reg-1-example", + scope: "SCOPE1", + registrationFront: "", + workers: [ + { + id: "id-worker-1-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, + { + id: "id-reg-2-example", + scope: "SCOPE2", + registrationFront: "", + workers: [ + { + id: "id-worker-2-example", + workerDescriptorFront: "", + url: "http://example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, + { + id: "id-reg-3-example", + scope: "SCOPE3", + registrationFront: "", + workers: [ + { + id: "id-worker-3-example", + workerDescriptorFront: "", + url: "http://different-example.com/worker.js", + state: 4, + stateText: "activated", + }, + ], + }, +]; + +// props for a simple manifest +const MANIFEST_SIMPLE = { + icons: [ + { + key: { sizes: "1x1", contentType: "image/png" }, + value: { src: "something.png", purpose: "any" }, + type: "icon", + }, + ], + identity: [{ key: "name", value: "foo", type: "string" }], + presentation: [ + { key: "lorem", value: "ipsum", type: "string" }, + { key: "foo", value: "bar", type: "string" }, + ], + validation: [{ level: "warning", message: "This is a warning" }], +}; + +// props for a manifest with string values +const MANIFEST_STRING_MEMBERS = { + icons: [], + identity: [{ key: "name", value: "foo", type: "string" }], + presentation: [], + validation: [], +}; + +// props for a manifest with color values +const MANIFEST_COLOR_MEMBERS = { + icons: [], + identity: [], + presentation: [ + { key: "background_color", value: "red", type: "color" }, + { key: "theme_color", value: "rgb(0, 0, 0)", type: "color" }, + ], + validation: [], +}; + +// props for a manifest with icon values +const MANIFEST_ICON_MEMBERS = { + icons: [ + { + key: { sizes: "1x1", contentType: "image/png" }, + value: { src: "something.png", purpose: "any" }, + type: "icon", + }, + { + key: { sizes: "", contentType: "" }, + value: { src: "something.svg", purpose: "any maskable" }, + type: "icon", + }, + ], + identity: [], + presentation: [], + validation: [], +}; + +// props for a manifest with values that have an unrecognized type +const MANIFEST_UNKNOWN_TYPE_MEMBERS = { + icons: [], + identity: [{ key: "lorem", value: "ipsum", type: "foo" }], + presentation: [], + validation: [], +}; + +// props for a manifest with url values +const MANIFEST_URL_MEMBERS = { + icons: [], + identity: [], + presentation: [ + { key: "start_url", value: "https://example.com/", type: "url" }, + { key: "scope", value: "https://example.com/", type: "url" }, + ], +}; + +const MANIFEST_WITH_ISSUES = { + icons: [], + identity: [{ key: "name", value: "foo", type: "string" }], + presentation: [ + { key: "lorem", value: "ipsum", type: "string" }, + { key: "foo", value: "bar", type: "string" }, + ], + validation: [{ level: "warning", message: "This is a warning" }], +}; + +// props for a manifest with no validation issues +const MANIFEST_NO_ISSUES = { + icons: [], + identity: [{ key: "name", value: "foo", type: "string" }], + presentation: [ + { key: "lorem", value: "ipsum", type: "string" }, + { key: "foo", value: "bar", type: "string" }, + ], + validation: [], +}; + +module.exports = { + // service worker related fixtures + EMPTY_WORKER_LIST, + MULTIPLE_WORKER_LIST, + MULTIPLE_WORKER_MIXED_DOMAINS_LIST, + REGISTRATION_MULTIPLE_WORKERS, + REGISTRATION_SINGLE_WORKER, + SINGLE_WORKER_DEFAULT_DOMAIN_LIST, + SINGLE_WORKER_DIFFERENT_DOMAIN_LIST, + WORKER_RUNNING, + WORKER_STOPPED, + WORKER_WAITING, + // manifest related fixtures + MANIFEST_NO_ISSUES, + MANIFEST_WITH_ISSUES, + MANIFEST_SIMPLE, + MANIFEST_COLOR_MEMBERS, + MANIFEST_ICON_MEMBERS, + MANIFEST_STRING_MEMBERS, + MANIFEST_UNKNOWN_TYPE_MEMBERS, + MANIFEST_URL_MEMBERS, +}; diff --git a/devtools/client/application/test/node/helpers.js b/devtools/client/application/test/node/helpers.js new file mode 100644 index 0000000000..6699549a15 --- /dev/null +++ b/devtools/client/application/test/node/helpers.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + thunk, +} = require("resource://devtools/client/shared/redux/middleware/thunk.js"); +const configureStore = require("redux-mock-store").default; + +/** + * Prepare the store for use in testing. + */ +function setupStore(preloadedState = {}) { + const middleware = [thunk()]; + const mockStore = configureStore(middleware); + return mockStore(preloadedState); +} + +/** + * This gives an opportunity to Promises to resolve in tests + * (since they are microtasks) + */ +async function flushPromises() { + await new Promise(r => setTimeout(r, 0)); +} + +module.exports = { + flushPromises, + setupStore, +}; diff --git a/devtools/client/application/test/node/jest.config.js b/devtools/client/application/test/node/jest.config.js new file mode 100644 index 0000000000..e114658f88 --- /dev/null +++ b/devtools/client/application/test/node/jest.config.js @@ -0,0 +1,14 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* global __dirname */ + +const sharedJestConfig = require(`${__dirname}/../../../shared/test-helpers/shared-jest.config`); + +module.exports = { + ...sharedJestConfig, + setupFiles: ["<rootDir>setup.js"], + snapshotSerializers: ["enzyme-to-json/serializer"], +}; diff --git a/devtools/client/application/test/node/package.json b/devtools/client/application/test/node/package.json new file mode 100644 index 0000000000..3bfea24f40 --- /dev/null +++ b/devtools/client/application/test/node/package.json @@ -0,0 +1,25 @@ +{ + "name": "application-panel-tests", + "license": "MPL-2.0", + "version": "0.0.1", + "engines": { + "node": ">=8.9.4" + }, + "scripts": { + "test": "jest", + "test-ci": "jest --json" + }, + "dependencies": { + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.13.2", + "enzyme-to-json": "^3.3.5", + "jest": "^24.6", + "react": "16.4.1", + "react-dom": "16", + "react-test-renderer": "16.4.1", + "redux": "^4.0.4", + "redux-mock-store": "^1.5.3" + }, + "devDependencies": {} +} diff --git a/devtools/client/application/test/node/setup.js b/devtools/client/application/test/node/setup.js new file mode 100644 index 0000000000..570e4462ae --- /dev/null +++ b/devtools/client/application/test/node/setup.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +// Configure enzyme with React 16 adapter. +const Enzyme = require("enzyme"); +const Adapter = require("enzyme-adapter-react-16"); +Enzyme.configure({ adapter: new Adapter() }); + +const { + setMocksInGlobal, +} = require("resource://devtools/client/shared/test-helpers/shared-node-helpers.js"); +setMocksInGlobal(); diff --git a/devtools/client/application/test/node/yarn.lock b/devtools/client/application/test/node/yarn.lock new file mode 100644 index 0000000000..91f1c4660c --- /dev/null +++ b/devtools/client/application/test/node/yarn.lock @@ -0,0 +1,3563 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.1.0": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.4" + "@babel/helpers" "^7.4.4" + "@babel/parser" "^7.4.5" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.5" + "@babel/types" "^7.4.4" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.0", "@babel/generator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" + dependencies: + "@babel/types" "^7.4.4" + jsesc "^2.5.1" + lodash "^4.17.11" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + +"@babel/helper-remap-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-wrap-function" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + dependencies: + "@babel/types" "^7.4.4" + +"@babel/helper-wrap-function@^7.1.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.2.0" + +"@babel/helpers@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5" + dependencies: + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" + +"@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + +"@babel/plugin-syntax-async-generators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216" + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.4" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.4.5" + "@babel/types" "^7.4.4" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.11" + +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" + dependencies: + esutils "^2.0.2" + lodash "^4.17.11" + to-fast-properties "^2.0.0" + +"@cnakazawa/watch@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@jest/console@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545" + dependencies: + "@jest/source-map" "^24.3.0" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/core@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b" + dependencies: + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.8.0" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" + jest-regex-util "^24.3.0" + jest-resolve-dependencies "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + jest-watcher "^24.8.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + pirates "^4.0.1" + realpath-native "^1.1.0" + rimraf "^2.5.4" + strip-ansi "^5.0.0" + +"@jest/environment@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac" + dependencies: + "@jest/fake-timers" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + +"@jest/fake-timers@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1" + dependencies: + "@jest/types" "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + +"@jest/reporters@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729" + dependencies: + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.1" + istanbul-reports "^2.1.1" + jest-haste-map "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + node-notifier "^5.2.1" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" + +"@jest/source-map@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28" + dependencies: + callsites "^3.0.0" + graceful-fs "^4.1.15" + source-map "^0.6.0" + +"@jest/test-result@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" + dependencies: + "@jest/console" "^24.7.1" + "@jest/types" "^24.8.0" + "@types/istanbul-lib-coverage" "^2.0.0" + +"@jest/test-sequencer@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b" + dependencies: + "@jest/test-result" "^24.8.0" + jest-haste-map "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" + +"@jest/transform@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.8.0" + babel-plugin-istanbul "^5.1.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.1.15" + jest-haste-map "^24.8.0" + jest-regex-util "^24.3.0" + jest-util "^24.8.0" + micromatch "^3.1.10" + realpath-native "^1.1.0" + slash "^2.0.0" + source-map "^0.6.1" + write-file-atomic "2.4.1" + +"@jest/types@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^12.0.9" + +"@types/babel__core@^7.1.0": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.7.tgz#2496e9ff56196cc1429c72034e07eab6121b6f3f" + dependencies: + "@babel/types" "^7.3.0" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + +"@types/istanbul-lib-report@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/node@*": + version "12.0.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.8.tgz#551466be11b2adc3f3d47156758f610bd9f6b1d8" + +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + +"@types/yargs@^12.0.2", "@types/yargs@^12.0.9": + version "12.0.12" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" + +abab@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +acorn-globals@^4.1.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.2.tgz#4e2c2313a597fd589720395f6354b41cd5ec8006" + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-walk@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913" + +acorn@^5.5.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + +acorn@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" + +airbnb-prop-types@^2.13.2: + version "2.13.2" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz#43147a5062dd2a4a5600e748a47b64004cc5f7fc" + dependencies: + array.prototype.find "^2.0.4" + function.prototype.name "^1.1.0" + has "^1.0.3" + is-regex "^1.0.4" + object-is "^1.0.1" + object.assign "^4.1.0" + object.entries "^1.1.0" + prop-types "^15.7.2" + prop-types-exact "^1.2.0" + react-is "^16.8.6" + +ajv@^6.5.5: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-regex@^4.0.0, ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + +array-filter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +array.prototype.find@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.0.tgz#630f2eaf70a39e608ac3573e45cf8ccd0ede9ad7" + dependencies: + define-properties "^1.1.3" + es-abstract "^1.13.0" + +array.prototype.flat@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.10.0" + function-bind "^1.1.1" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + +babel-jest@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" + dependencies: + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.6.0" + chalk "^2.4.2" + slash "^2.0.0" + +babel-plugin-istanbul@^5.1.0: + version "5.1.4" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz#841d16b9a58eeb407a0ddce622ba02fe87a752ba" + dependencies: + find-up "^3.0.0" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" + +babel-plugin-jest-hoist@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019" + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-preset-jest@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984" + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.6.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + dependencies: + tweetnacl "^0.14.3" + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browser-process-hrtime@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" + +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + dependencies: + resolve "1.1.7" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + dependencies: + rsvp "^4.8.4" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.1" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + dependencies: + delayed-stream "~1.0.0" + +commander@^2.19.0, commander@~2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +convert-source-map@^1.1.0, convert-source-map@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.6" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.6.tgz#f85206cee04efa841f3c5982a74ba96ab20d65ad" + +cssstyle@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.2.2.tgz#427ea4d585b18624f6fdbf9de7a2a1a3ba713077" + dependencies: + cssom "0.3.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + +diff-sequences@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" + +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + +dom-serializer@0, dom-serializer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + dependencies: + webidl-conversions "^4.0.2" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + dependencies: + domelementtype "1" + +domutils@1.5.1, domutils@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + +enzyme-adapter-react-16@^1.13.2: + version "1.14.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz#204722b769172bcf096cb250d33e6795c1f1858f" + dependencies: + enzyme-adapter-utils "^1.12.0" + has "^1.0.3" + object.assign "^4.1.0" + object.values "^1.1.0" + prop-types "^15.7.2" + react-is "^16.8.6" + react-test-renderer "^16.0.0-0" + semver "^5.7.0" + +enzyme-adapter-utils@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz#96e3730d76b872f593e54ce1c51fa3a451422d93" + dependencies: + airbnb-prop-types "^2.13.2" + function.prototype.name "^1.1.0" + object.assign "^4.1.0" + object.fromentries "^2.0.0" + prop-types "^15.7.2" + semver "^5.6.0" + +enzyme-to-json@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.5.tgz#f8eb82bd3d5941c9d8bc6fd9140030777d17d0af" + dependencies: + lodash "^4.17.4" + +enzyme@^3.9.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6" + dependencies: + array.prototype.flat "^1.2.1" + cheerio "^1.0.0-rc.2" + function.prototype.name "^1.1.0" + has "^1.0.3" + html-element-map "^1.0.0" + is-boolean-object "^1.0.0" + is-callable "^1.1.4" + is-number-object "^1.0.3" + is-regex "^1.0.4" + is-string "^1.0.4" + is-subset "^0.1.1" + lodash.escape "^4.0.1" + lodash.isequal "^4.5.0" + object-inspect "^1.6.0" + object-is "^1.0.1" + object.assign "^4.1.0" + object.entries "^1.0.4" + object.values "^1.0.4" + raf "^3.4.0" + rst-selector-parser "^2.2.3" + string.prototype.trim "^1.1.2" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13.0, es-abstract@^1.5.0, es-abstract@^1.5.1: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@^1.9.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + +estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +exec-sh@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d" + dependencies: + "@jest/types" "^24.8.0" + ansi-styles "^3.2.0" + jest-get-type "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-regex-util "^24.3.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + +fbjs@^0.8.16: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + +fs-minipass@^1.2.5: + version "1.2.6" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" + dependencies: + minipass "^2.2.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.2.7: + version "1.2.9" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.12.0" + +function-bind@^1.0.2, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +function.prototype.name@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + is-callable "^1.1.3" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + +handlebars@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + +html-element-map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.0.1.tgz#3c4fcb4874ebddfe4283b51c8994e7713782b592" + dependencies: + array-filter "^1.0.0" + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + dependencies: + whatwg-encoding "^1.0.1" + +htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-boolean-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-callable@^1.1.3, is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + dependencies: + ci-info "^2.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + +is-number-object@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-string@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + dependencies: + has-symbols "^1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" + +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + dependencies: + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^2.1.1: + version "2.2.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" + dependencies: + handlebars "^4.1.2" + +jest-changed-files@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b" + dependencies: + "@jest/types" "^24.8.0" + execa "^1.0.0" + throat "^4.0.0" + +jest-cli@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989" + dependencies: + "@jest/core" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + exit "^0.1.2" + import-local "^2.0.0" + is-ci "^2.0.0" + jest-config "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^12.0.2" + +jest-config@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f" + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^24.8.0" + "@jest/types" "^24.8.0" + babel-jest "^24.8.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.8.0" + jest-environment-node "^24.8.0" + jest-get-type "^24.8.0" + jest-jasmine2 "^24.8.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + micromatch "^3.1.10" + pretty-format "^24.8.0" + realpath-native "^1.1.0" + +jest-diff@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172" + dependencies: + chalk "^2.0.1" + diff-sequences "^24.3.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" + +jest-docblock@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd" + dependencies: + detect-newline "^2.1.0" + +jest-each@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775" + dependencies: + "@jest/types" "^24.8.0" + chalk "^2.0.1" + jest-get-type "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" + +jest-environment-jsdom@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" + dependencies: + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" + jsdom "^11.5.1" + +jest-environment-node@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231" + dependencies: + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" + +jest-get-type@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc" + +jest-haste-map@^24.8.0: + version "24.8.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.1.tgz#f39cc1d2b1d907e014165b4bd5a957afcb992982" + dependencies: + "@jest/types" "^24.8.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.4.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + +jest-jasmine2@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898" + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.8.0" + is-generator-fn "^2.0.0" + jest-each "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" + throat "^4.0.0" + +jest-leak-detector@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980" + dependencies: + pretty-format "^24.8.0" + +jest-matcher-utils@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495" + dependencies: + chalk "^2.0.1" + jest-diff "^24.8.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" + +jest-message-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b" + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + +jest-mock@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" + dependencies: + "@jest/types" "^24.8.0" + +jest-pnp-resolver@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" + +jest-regex-util@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" + +jest-resolve-dependencies@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0" + dependencies: + "@jest/types" "^24.8.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.8.0" + +jest-resolve@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f" + dependencies: + "@jest/types" "^24.8.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + +jest-runner@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb" + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.8.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.8.0" + jest-jasmine2 "^24.8.0" + jest-leak-detector "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + source-map-support "^0.5.6" + throat "^4.0.0" + +jest-runtime@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620" + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/yargs" "^12.0.2" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^12.0.2" + +jest-serializer@^24.4.0: + version "24.4.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3" + +jest-snapshot@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6" + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + expect "^24.8.0" + jest-diff "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.8.0" + semver "^5.5.0" + +jest-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" + dependencies: + "@jest/console" "^24.7.1" + "@jest/fake-timers" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + +jest-validate@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849" + dependencies: + "@jest/types" "^24.8.0" + camelcase "^5.0.0" + chalk "^2.0.1" + jest-get-type "^24.8.0" + leven "^2.1.0" + pretty-format "^24.8.0" + +jest-watcher@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4" + dependencies: + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/yargs" "^12.0.9" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.8.0" + string-length "^2.0.0" + +jest-worker@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3" + dependencies: + merge-stream "^1.0.1" + supports-color "^6.1.0" + +jest@^24.6: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081" + dependencies: + import-local "^2.0.0" + jest-cli "^24.8.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsdom@^11.5.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + dependencies: + minimist "^1.2.0" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +kleur@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + dependencies: + invert-kv "^2.0.0" + +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" + +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + +lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.4: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + +merge-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + dependencies: + readable-stream "^2.0.1" + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + dependencies: + mime-db "1.40.0" + +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8, minimist@~0.0.1: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minipass@^2.2.1, minipass@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +moo@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +nearley@^2.7.10: + version "2.16.0" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.16.0.tgz#77c297d041941d268290ec84b739d0ee297e83a7" + dependencies: + commander "^2.19.0" + moo "^0.4.3" + railroad-diagrams "^1.0.0" + randexp "0.4.6" + semver "^5.4.1" + +needle@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +neo-async@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + +node-notifier@^5.2.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a" + dependencies: + growly "^1.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" + shellwords "^0.1.1" + which "^1.3.0" + +node-pre-gyp@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +npm-bundled@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + +npm-packlist@^1.1.6: + version "1.4.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + dependencies: + boolbase "~1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +nwsapi@^2.0.7: + version "2.1.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + +object-keys@^1.0.11, object-keys@^1.0.12: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.entries@^1.0.4, object.entries@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" + dependencies: + define-properties "^1.1.3" + es-abstract "^1.12.0" + function-bind "^1.1.1" + has "^1.0.3" + +object.fromentries@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.11.0" + function-bind "^1.1.1" + has "^1.0.1" + +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + +object.values@^1.0.4, object.values@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9" + dependencies: + define-properties "^1.1.3" + es-abstract "^1.12.0" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + dependencies: + p-reduce "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + +p-limit@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + dependencies: + p-limit "^2.0.0" + +p-reduce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + dependencies: + "@types/node" "*" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + dependencies: + find-up "^3.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +pretty-format@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" + dependencies: + "@jest/types" "^24.8.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prompts@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.1.0.tgz#bf90bc71f6065d255ea2bdc0fe6520485c1b45db" + dependencies: + kleur "^3.0.2" + sisteransi "^1.0.0" + +prop-types-exact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" + dependencies: + has "^1.0.3" + object.assign "^4.1.0" + reflect.ownkeys "^0.2.0" + +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +psl@^1.1.24, psl@^1.1.28: + version "1.1.32" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.32.tgz#3f132717cf2f9c169724b2b6caf373cf694198db" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + +raf@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + dependencies: + performance-now "^2.1.0" + +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + +randexp@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-dom@16: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.6" + +react-is@^16.4.1, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + +react-test-renderer@16.4.1, react-test-renderer@^16.0.0-0: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70" + dependencies: + fbjs "^0.8.16" + object-assign "^4.1.1" + prop-types "^15.6.0" + react-is "^16.4.1" + +react@16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +readable-stream@^2.0.1, readable-stream@^2.0.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +realpath-native@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" + dependencies: + util.promisify "^1.0.0" + +redux-mock-store@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.3.tgz#1f10528949b7ce8056c2532624f7cafa98576c6d" + dependencies: + lodash.isplainobject "^4.0.6" + +redux@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +reflect.ownkeys@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +request-promise-core@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" + dependencies: + lodash "^4.17.11" + +request-promise-native@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" + dependencies: + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.87.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.10.0, resolve@^1.3.2: + version "1.11.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232" + dependencies: + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + dependencies: + glob "^7.1.3" + +rst-selector-parser@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" + dependencies: + lodash.flattendeep "^4.4.0" + nearley "^2.7.10" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +scheduler@^0.13.6: + version "0.13.6" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + +semver@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +sisteransi@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.6: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + dependencies: + astral-regex "^1.0.0" + strip-ansi "^4.0.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string.prototype.trim@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.0" + function-bind "^1.0.2" + +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + dependencies: + has-flag "^3.0.0" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + +tar@^4: + version "4.4.10" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.5" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^2.0.0" + +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +tough-cookie@^2.3.3, tough-cookie@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + dependencies: + punycode "^2.1.0" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +ua-parser-js@^0.7.18: + version "0.7.20" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" + +uglify-js@^3.1.4: + version "3.6.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" + dependencies: + commander "~2.20.0" + source-map "~0.6.1" + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +w3c-hr-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" + dependencies: + browser-process-hrtime "^0.1.2" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + dependencies: + iconv-lite "0.4.24" + +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd" + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.2.9, which@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + dependencies: + string-width "^1.0.2 || 2" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + +"y18n@^3.2.1 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + +yallist@^3.0.0, yallist@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + +yargs-parser@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^12.0.2: + version "12.0.5" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" + dependencies: + cliui "^4.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^3.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^11.1.1" diff --git a/devtools/client/application/test/xpcshell/.eslintrc.js b/devtools/client/application/test/xpcshell/.eslintrc.js new file mode 100644 index 0000000000..8611c174f5 --- /dev/null +++ b/devtools/client/application/test/xpcshell/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the common devtools xpcshell eslintrc config. + extends: "../../../../.eslintrc.xpcshell.js", +}; diff --git a/devtools/client/application/test/xpcshell/test_manifest_reducer.js b/devtools/client/application/test/xpcshell/test_manifest_reducer.js new file mode 100644 index 0000000000..2e91b57442 --- /dev/null +++ b/devtools/client/application/test/xpcshell/test_manifest_reducer.js @@ -0,0 +1,201 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + FETCH_MANIFEST_FAILURE, + FETCH_MANIFEST_START, + FETCH_MANIFEST_SUCCESS, + RESET_MANIFEST, + MANIFEST_MEMBER_VALUE_TYPES, +} = require("resource://devtools/client/application/src/constants.js"); + +const { ICON, COLOR, STRING, URL } = MANIFEST_MEMBER_VALUE_TYPES; + +const { + manifestReducer, + ManifestState, +} = require("resource://devtools/client/application/src/reducers/manifest-state.js"); + +const MANIFEST_PROCESSING = [ + // empty manifest + { + source: {}, + processed: {}, + }, + // manifest with just one member + { + source: { name: "Foo" }, + processed: { + identity: [{ key: "name", value: "Foo", type: STRING }], + }, + }, + // manifest with two members from the same category + { + source: { + short_name: "Short Foo", + name: "Long Foo", + }, + processed: { + identity: [ + { key: "short_name", value: "Short Foo", type: STRING }, + { key: "name", value: "Long Foo", type: STRING }, + ], + }, + }, + // manifest with members from two different categories + { + source: { + name: "Foo", + background_color: "#FF0000", + start_url: "https://example.com/?q=foo", + scope: "https://example.com", + }, + processed: { + identity: [{ key: "name", value: "Foo", type: STRING }], + presentation: [ + { key: "background_color", value: "#FF0000", type: COLOR }, + { key: "start_url", value: "https://example.com/?q=foo", type: URL }, + { key: "scope", value: "https://example.com", type: URL }, + ], + }, + }, + // manifest with icons + { + source: { + icons: [ + { + src: "something.png", + type: "image/png", + sizes: ["16x16", "32x32"], + purpose: ["any"], + }, + { + src: "another.svg", + type: "image/svg", + sizes: ["any"], + purpose: ["any maskable"], + }, + { + src: "something.png", + type: undefined, + sizes: undefined, + purpose: ["any"], + }, + ], + }, + processed: { + icons: [ + { + key: { sizes: "16x16 32x32", contentType: "image/png" }, + value: { src: "something.png", purpose: "any" }, + type: ICON, + }, + { + key: { sizes: "any", contentType: "image/svg" }, + value: { src: "another.svg", purpose: "any maskable" }, + type: ICON, + }, + { + key: { sizes: undefined, contentType: undefined }, + value: { src: "something.png", purpose: "any" }, + type: ICON, + }, + ], + }, + }, + // manifest with issues + { + source: { + moz_validation: [ + { warn: "A warning" }, + { error: "An error", type: "json" }, + ], + }, + processed: { + validation: [ + { level: "warning", message: "A warning", type: null }, + { level: "error", message: "An error", type: "json" }, + ], + }, + }, + // manifest with URL + { + source: { + moz_manifest_url: "https://example.com/manifest.json", + }, + processed: { + url: "https://example.com/manifest.json", + }, + }, +]; + +add_task(async function () { + info("Test manifest reducer: FETCH_MANIFEST_START action"); + + const state = ManifestState(); + const action = { type: FETCH_MANIFEST_START }; + const newState = manifestReducer(state, action); + + equal(newState.isLoading, true, "Loading flag is true"); +}); + +add_task(async function () { + info("Test manifest reducer: FETCH_MANIFEST_FAILURE action"); + + const state = Object.assign(ManifestState(), { isLoading: true }); + const action = { type: FETCH_MANIFEST_FAILURE, error: "some error" }; + const newState = manifestReducer(state, action); + + equal(newState.errorMessage, "some error", "Error message is as expected"); + equal(newState.isLoading, false, "Loading flag is false"); + equal(newState.manifest, null, "Manifest is null"); +}); + +add_task(async function () { + info("Test manifest reducer: FETCH_MANIFEST_SUCCESS action"); + + // test manifest processing + MANIFEST_PROCESSING.forEach(({ source, processed }) => { + test_manifest_processing(source, processed); + }); +}); + +add_task(async function () { + info("Test manifest reducer: RESET_MANIFEST action"); + + const state = Object.assign(ManifestState(), { + isLoading: true, + manifest: { identity: [{ key: "name", value: "Foo" }] }, + errorMessage: "some error", + }); + const action = { type: RESET_MANIFEST }; + const newState = manifestReducer(state, action); + + deepEqual(newState, ManifestState(), "Manifest has been reset to defaults"); +}); + +function test_manifest_processing(source, processed) { + const state = ManifestState(); + state.isLoading = true; + + const action = { type: FETCH_MANIFEST_SUCCESS, manifest: source }; + const newState = manifestReducer(state, action); + + // merge the expected processed manifst with some default values + const expected = Object.assign( + { + icons: [], + identity: [], + presentation: [], + url: undefined, + validation: [], + }, + processed + ); + + deepEqual(newState.manifest, expected, "Processed manifest as expected"); + equal(newState.errorMessage, "", "Error message is empty"); + equal(newState.isLoading, false, "Loading flag is false"); +} diff --git a/devtools/client/application/test/xpcshell/test_page_reducer.js b/devtools/client/application/test/xpcshell/test_page_reducer.js new file mode 100644 index 0000000000..9aecfc67bb --- /dev/null +++ b/devtools/client/application/test/xpcshell/test_page_reducer.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + updateDomain, +} = require("resource://devtools/client/application/src/actions/page.js"); + +const { + pageReducer, + PageState, +} = require("resource://devtools/client/application/src/reducers/page-state.js"); + +add_task(async function () { + info("Test page reducer: UPDATE_DOMAIN action"); + const state = PageState(); + const action = updateDomain("https://example.com/foo/#bar"); + + const newState = pageReducer(state, action); + equal(newState.domain, "example.com"); +}); diff --git a/devtools/client/application/test/xpcshell/test_ui_reducer.js b/devtools/client/application/test/xpcshell/test_ui_reducer.js new file mode 100644 index 0000000000..22c1844238 --- /dev/null +++ b/devtools/client/application/test/xpcshell/test_ui_reducer.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + updateSelectedPage, +} = require("resource://devtools/client/application/src/actions/ui.js"); + +const { + uiReducer, + UiState, +} = require("resource://devtools/client/application/src/reducers/ui-state.js"); + +add_task(async function () { + info("Test ui reducer: UPDATE_SELECTED_PAGE action"); + const state = UiState(); + const action = updateSelectedPage("foo"); + + const newState = uiReducer(state, action); + equal(newState.selectedPage, "foo"); +}); diff --git a/devtools/client/application/test/xpcshell/test_workers_reducer.js b/devtools/client/application/test/xpcshell/test_workers_reducer.js new file mode 100644 index 0000000000..a662c313df --- /dev/null +++ b/devtools/client/application/test/xpcshell/test_workers_reducer.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + updateCanDebugWorkers, + updateWorkers, +} = require("resource://devtools/client/application/src/actions/workers.js"); + +const { + START_WORKER, + UNREGISTER_WORKER, +} = require("resource://devtools/client/application/src/constants.js"); + +const { + workersReducer, + WorkersState, +} = require("resource://devtools/client/application/src/reducers/workers-state.js"); + +add_task(async function () { + info("Test workers reducer: UPDATE_CAN_DEBUG_WORKERS action"); + + function testUpdateCanDebugWorkers(flagValue) { + const state = WorkersState(); + const action = updateCanDebugWorkers(flagValue); + const newState = workersReducer(state, action); + equal( + newState.canDebugWorkers, + flagValue, + "canDebugWorkers contains the expected value" + ); + } + + testUpdateCanDebugWorkers(false); + testUpdateCanDebugWorkers(true); +}); + +add_task(async function () { + info("Test workers reducer: UPDATE_WORKERS action"); + const state = WorkersState(); + + const rawData = [ + { + registration: { + scope: "lorem-ipsum", + lastUpdateTime: 42, + id: "r1", + }, + workers: [ + { + id: "w1", + state: Ci.nsIServiceWorkerInfo.STATE_ACTIVATED, + url: "https://example.com/w1.js", + workerDescriptorFront: { foo: "bar" }, + stateText: "activated", + }, + { + id: "w2", + state: Ci.nsIServiceWorkerInfo.STATE_INSTALLED, + url: "https://example.com/w2.js", + workerDescriptorFront: undefined, + stateText: "installed", + }, + ], + }, + ]; + + const expectedData = [ + { + id: "r1", + lastUpdateTime: 42, + registrationFront: rawData[0].registration, + scope: "lorem-ipsum", + workers: [ + { + id: "w1", + url: "https://example.com/w1.js", + workerDescriptorFront: rawData[0].workers[0].workerDescriptorFront, + registrationFront: rawData[0].registration, + state: Ci.nsIServiceWorkerInfo.STATE_ACTIVATED, + stateText: "activated", + }, + { + id: "w2", + url: "https://example.com/w2.js", + workerDescriptorFront: undefined, + registrationFront: rawData[0].registration, + state: Ci.nsIServiceWorkerInfo.STATE_INSTALLED, + stateText: "installed", + }, + ], + }, + ]; + + const action = updateWorkers(rawData); + const newState = workersReducer(state, action); + deepEqual(newState.list, expectedData, "workers contains the expected list"); +}); + +add_task(async function () { + info("Test workers reducer: START_WORKER action"); + const state = WorkersState(); + const action = { type: START_WORKER }; + const newState = workersReducer(state, action); + deepEqual(state, newState, "workers state stays the same"); +}); + +add_task(async function () { + info("Test workers reducer: UNREGISTER_WORKER action"); + const state = WorkersState(); + const action = { type: UNREGISTER_WORKER }; + const newState = workersReducer(state, action); + deepEqual(state, newState, "workers state stays the same"); +}); diff --git a/devtools/client/application/test/xpcshell/xpcshell-head.js b/devtools/client/application/test/xpcshell/xpcshell-head.js new file mode 100644 index 0000000000..733c0400da --- /dev/null +++ b/devtools/client/application/test/xpcshell/xpcshell-head.js @@ -0,0 +1,10 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ + +const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" +); diff --git a/devtools/client/application/test/xpcshell/xpcshell.toml b/devtools/client/application/test/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..1cf00f01c1 --- /dev/null +++ b/devtools/client/application/test/xpcshell/xpcshell.toml @@ -0,0 +1,13 @@ +[DEFAULT] +tags = "devtools" +head = "xpcshell-head.js" +firefox-appdir = "browser" +skip-if = ["os == 'android'"] + +["test_manifest_reducer.js"] + +["test_page_reducer.js"] + +["test_ui_reducer.js"] + +["test_workers_reducer.js"] |