summaryrefslogtreecommitdiffstats
path: root/devtools/client/application/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/application/test')
-rw-r--r--devtools/client/application/test/browser/browser.ini80
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_debug-service-worker.js61
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-domain-workers.js70
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-multiple-workers-same-registration.js64
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-several-workers.js54
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-single-worker.js64
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-unicode.js47
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_list-workers-empty.js29
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-display.js145
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-load.js67
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-open-json.js67
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_manifest-reload.js51
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_open-links.js48
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_sidebar.js82
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_start-service-worker.js54
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_target-switching.js68
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-debug-worker.js49
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-select-page.js26
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-start-worker.js45
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_telemetry-unregister-worker.js37
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_unregister-worker.js36
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_viewsource-service-worker.js50
-rw-r--r--devtools/client/application/test/browser/browser_application_panel_worker-states.js62
-rw-r--r--devtools/client/application/test/browser/head.js134
-rw-r--r--devtools/client/application/test/browser/resources/manifest/icon.svg4
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-fail.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-no-manifest.html8
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-icons.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-json-error.html10
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-manifest-link.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok-warnings.html10
-rw-r--r--devtools/client/application/test/browser/resources/manifest/load-ok.html9
-rw-r--r--devtools/client/application/test/browser/resources/manifest/manifest.json3
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/controlled-install-sw.js29
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/controlled-install.html27
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/debug-sw.js18
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/debug.html25
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/dynamic-registration.html19
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/empty-sw.js4
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/empty.html11
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/scope-page.html19
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/simple-unicode.html15
-rw-r--r--devtools/client/application/test/browser/resources/service-workers/simple.html32
-rw-r--r--devtools/client/application/test/node/.eslintrc.js10
-rw-r--r--devtools/client/application/test/node/actions/actions_application_panel-manifest.test.js83
-rw-r--r--devtools/client/application/test/node/babel.config.js8
-rw-r--r--devtools/client/application/test/node/components/__snapshots__/components_application_panel-App.test.js.snap12
-rw-r--r--devtools/client/application/test/node/components/components_application_panel-App.test.js26
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap396
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap58
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap46
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap106
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap49
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap89
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap35
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap26
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap50
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap80
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap30
-rw-r--r--devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestUrlItem.test.js.snap21
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-Manifest.test.js73
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestColorItem.test.js48
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestEmpty.test.js23
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIconItem.test.js48
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssue.test.js30
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssueList.test.js59
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestItem.test.js28
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestJsonLink.test.js36
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestLoader.test.js82
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestPage.test.js59
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestSection.test.js34
-rw-r--r--devtools/client/application/test/node/components/manifest/components_application_panel-ManifestUrlItem.test.js30
-rw-r--r--devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap9
-rw-r--r--devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap64
-rw-r--r--devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap141
-rw-r--r--devtools/client/application/test/node/components/routing/components_application_panel-PageSwitcher.test.js73
-rw-r--r--devtools/client/application/test/node/components/routing/components_application_panel-Sidebar.test.js51
-rw-r--r--devtools/client/application/test/node/components/routing/components_application_panel-SidebarItem.test.js82
-rw-r--r--devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Registration.test.js.snap180
-rw-r--r--devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationList.test.js.snap159
-rw-r--r--devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-RegistrationListEmpty.test.js.snap66
-rw-r--r--devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-Worker.test.js.snap132
-rw-r--r--devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap143
-rw-r--r--devtools/client/application/test/node/components/service-workers/components_application_panel-Registration.test.js88
-rw-r--r--devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationList.test.js43
-rw-r--r--devtools/client/application/test/node/components/service-workers/components_application_panel-RegistrationListEmpty.test.js23
-rw-r--r--devtools/client/application/test/node/components/service-workers/components_application_panel-Worker.test.js110
-rw-r--r--devtools/client/application/test/node/components/service-workers/components_application_panel-WorkersPage.test.js82
-rw-r--r--devtools/client/application/test/node/fixtures/data/constants.js312
-rw-r--r--devtools/client/application/test/node/helpers.js31
-rw-r--r--devtools/client/application/test/node/jest.config.js14
-rw-r--r--devtools/client/application/test/node/package.json25
-rw-r--r--devtools/client/application/test/node/setup.js15
-rw-r--r--devtools/client/application/test/node/yarn.lock3563
-rw-r--r--devtools/client/application/test/xpcshell/.eslintrc.js6
-rw-r--r--devtools/client/application/test/xpcshell/test_manifest_reducer.js201
-rw-r--r--devtools/client/application/test/xpcshell/test_page_reducer.js22
-rw-r--r--devtools/client/application/test/xpcshell/test_ui_reducer.js22
-rw-r--r--devtools/client/application/test/xpcshell/test_workers_reducer.js115
-rw-r--r--devtools/client/application/test/xpcshell/xpcshell-head.js10
-rw-r--r--devtools/client/application/test/xpcshell/xpcshell.ini10
101 files changed, 9227 insertions, 0 deletions
diff --git a/devtools/client/application/test/browser/browser.ini b/devtools/client/application/test/browser/browser.ini
new file mode 100644
index 0000000000..4150959be4
--- /dev/null
+++ b/devtools/client/application/test/browser/browser.ini
@@ -0,0 +1,80 @@
+[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
+
+# Worker-related tests
+[browser_application_panel_debug-service-worker.js]
+skip-if = debug || asan || !serviceworker_e10s # Bug 1559591, 1575578, 1588154
+[browser_application_panel_list-domain-workers.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_list-multiple-workers-same-registration.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_list-several-workers.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_list-single-worker.js]
+https_first_disabled = true
+skip-if = debug # Bug 1559591
+[browser_application_panel_start-service-worker.js]
+skip-if = asan || debug || !serviceworker_e10s || tsan # Bug 1559487, 1559591, 1608640
+[browser_application_panel_list-workers-empty.js]
+[browser_application_panel_list-unicode.js]
+skip-if = debug # Bug 1559591
+[browser_application_panel_unregister-worker.js]
+skip-if = debug # Bug 1559591
+[browser_application_panel_viewsource-service-worker.js]
+https_first_disabled = true
+skip-if = debug || asan || !serviceworker_e10s # Bug 1559591, 1575578, 1588154
+[browser_application_panel_worker-states.js]
+skip-if = asan || debug || !serviceworker_e10s # Bug 1559487, 1559591, 1608640
+# Manifest-related tests
+[browser_application_panel_manifest-display.js]
+[browser_application_panel_manifest-load.js]
+[browser_application_panel_manifest-open-json.js]
+https_first_disabled = true
+[browser_application_panel_manifest-reload.js]
+https_first_disabled = true
+# Telemetry tests
+[browser_application_panel_telemetry-debug-worker.js]
+https_first_disabled = true
+skip-if =
+ asan || debug || !serviceworker_e10s # Bug 1559487, 1559591, 1608640
+ os == 'linux' && bits == 64 && !debug # Bug 1654354
+[browser_application_panel_telemetry-select-page.js]
+[browser_application_panel_telemetry-start-worker.js]
+skip-if = ccov || asan || debug || !serviceworker_e10s || tsan # Bug 1559487, 1559591, 1608640, 1654468
+[browser_application_panel_telemetry-unregister-worker.js]
+skip-if = asan || debug || !serviceworker_e10s # Bug 1559487, 1559591, 1608640
+# Misc tests
+[browser_application_panel_open-links.js]
+skip-if = true # Bug 1467256, 1559591
+[browser_application_panel_sidebar.js]
+[browser_application_panel_target-switching.js]
+https_first_disabled = true
+skip-if = (os == 'win') || (os == 'linux') # Bug 1640234
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..714fcc276e
--- /dev/null
+++ b/devtools/client/application/test/browser/browser_application_panel_manifest-display.js
@@ -0,0 +1,145 @@
+/* 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");
+
+ ok(
+ 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");
+ ok(issuesEl !== null, "Validation issues are displayed");
+
+ const warningEl = [...issuesEl.querySelectorAll(".js-manifest-issue")].find(
+ x => x.textContent.includes("background_color")
+ );
+ ok(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");
+ ok(issuesEl !== null, "Validation issues are displayed");
+
+ const errorEl = [...issuesEl.querySelectorAll(".js-manifest-issue")].find(x =>
+ x.textContent.includes("JSON")
+ );
+ ok(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");
+ ok(iconEl !== null, "Icon label is being displayed with size and image type");
+ const imgEl = iconEl.querySelector(".js-manifest-item-content img");
+ ok(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..3cf46f07b1
--- /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="chrome://devtools/skin/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.ini b/devtools/client/application/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..824750f9f5
--- /dev/null
+++ b/devtools/client/application/test/xpcshell/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+tags = devtools
+head = xpcshell-head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_manifest_reducer.js]
+[test_page_reducer.js]
+[test_ui_reducer.js]
+[test_workers_reducer.js]