This is an automated email from the ASF dual-hosted git repository.

yjc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 4342c33  build: enable typescript for cypress (#10170)
4342c33 is described below

commit 4342c33d0ebb2bbce1969a4072ce13a1b0af283c
Author: Jesse Yang <jesse.y...@airbnb.com>
AuthorDate: Mon Jun 29 10:53:33 2020 -0700

    build: enable typescript for cypress (#10170)
---
 .github/workflows/prefer_typescript.yml            |  5 +-
 superset-frontend/cypress-base/.eslintrc           | 25 +++++++
 superset-frontend/cypress-base/cypress.json        |  3 +-
 superset-frontend/cypress-base/cypress/.eslintrc   |  8 ---
 .../dashboard/{filter.test.js => filter.test.ts}   | 41 +++++++----
 .../cypress/integration/dashboard/save.test.js     |  5 --
 .../cypress/integration/explore/link.test.js       |  2 -
 .../cypress/integration/sqllab/tabs.test.js        | 20 +++---
 .../cypress-base/cypress/plugins/index.js          | 10 ---
 .../cypress-base/cypress/support/index.d.ts        | 61 ++++++++++++++++
 .../cypress-base/cypress/support/index.js          | 42 -----------
 .../cypress/support/{commands.js => index.ts}      | 84 ++++++++++------------
 .../{readResponseBlob.js => readResponseBlob.ts}   | 21 +++---
 superset-frontend/cypress-base/tsconfig.json       | 12 ++++
 14 files changed, 186 insertions(+), 153 deletions(-)

diff --git a/.github/workflows/prefer_typescript.yml 
b/.github/workflows/prefer_typescript.yml
index db1c926..2572ef3 100644
--- a/.github/workflows/prefer_typescript.yml
+++ b/.github/workflows/prefer_typescript.yml
@@ -21,10 +21,7 @@ jobs:
           js_files_added() {
             jq -r '
               map(
-                select(
-                  (contains("cypress-base/") | not) and
-                  (endswith(".js") or endswith(".jsx"))
-                )
+                select((endswith(".js") or endswith(".jsx"))
               ) | join("\n")
             ' ${HOME}/files_added.json
           }
diff --git a/superset-frontend/cypress-base/.eslintrc 
b/superset-frontend/cypress-base/.eslintrc
new file mode 100644
index 0000000..91d33ba
--- /dev/null
+++ b/superset-frontend/cypress-base/.eslintrc
@@ -0,0 +1,25 @@
+{
+  "parser": "@typescript-eslint/parser",
+  "plugins": ["cypress", "@typescript-eslint"],
+  "extends": [
+    "plugin:@typescript-eslint/recommended",
+    "plugin:cypress/recommended"
+  ],
+  "rules": {
+    "import/no-unresolved": 0,
+    "@typescript-eslint/explicit-function-return-type": 0,
+    "@typescript-eslint/explicit-module-boundary-types": 0,
+    "@typescript-eslint/no-var-requires": 0,
+    "@typescript-eslint/camelcase": 0
+  },
+  "settings": {
+    "import/resolver": {
+      "node": {
+        "extensions": [".js", ".jsx", ".ts", ".tsx"]
+      }
+    }
+  },
+  "env": {
+    "cypress/globals": true
+  }
+}
diff --git a/superset-frontend/cypress-base/cypress.json 
b/superset-frontend/cypress-base/cypress.json
index 76e4778..8856588 100644
--- a/superset-frontend/cypress-base/cypress.json
+++ b/superset-frontend/cypress-base/cypress.json
@@ -2,9 +2,10 @@
   "baseUrl": "http://localhost:8081";,
   "chromeWebSecurity": false,
   "defaultCommandTimeout": 5000,
+  "experimentalFetchPolyfill": true,
   "requestTimeout": 10000,
   "ignoreTestFiles": [
-    "**/!(*.test.js)"
+    "**/!(*.test.js|*.test.ts)"
   ],
   "video": false,
   "videoUploadOnPasses": false,
diff --git a/superset-frontend/cypress-base/cypress/.eslintrc 
b/superset-frontend/cypress-base/cypress/.eslintrc
deleted file mode 100644
index 5b98856..0000000
--- a/superset-frontend/cypress-base/cypress/.eslintrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "plugins": [
-    "cypress"
-  ],
-  "env": {
-    "cypress/globals": true
-  }
-}
diff --git 
a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.js 
b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts
similarity index 79%
rename from 
superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.js
rename to 
superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts
index d734a57..6f38600 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.js
+++ 
b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts
@@ -18,11 +18,23 @@
  */
 import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
 
+interface Slice {
+  slice_id: number;
+  form_data: {
+    viz_type: string;
+    [key: string]: JSONValue;
+  };
+}
+
+interface DashboardData {
+  slices: Slice[];
+}
+
 describe('Dashboard filter', () => {
-  let filterId;
-  let aliases;
+  let filterId: number;
+  let aliases: string[];
 
-  const getAlias = id => {
+  const getAlias = (id: number) => {
     return `@slice_${id}`;
   };
 
@@ -32,13 +44,14 @@ describe('Dashboard filter', () => {
 
     cy.visit(WORLD_HEALTH_DASHBOARD);
 
-    cy.get('#app').then(data => {
-      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-      const dashboard = bootstrapData.dashboard_data;
+    cy.get('#app').then(app => {
+      const bootstrapData = app.data('bootstrap');
+      const dashboard = bootstrapData.dashboard_data as DashboardData;
       const sliceIds = dashboard.slices.map(slice => slice.slice_id);
-      filterId = dashboard.slices.find(
-        slice => slice.form_data.viz_type === 'filter_box',
-      ).slice_id;
+      filterId =
+        dashboard.slices.find(
+          slice => slice.form_data.viz_type === 'filter_box',
+        )?.slice_id || 0;
       aliases = sliceIds.map(id => {
         const alias = getAlias(id);
         const url = `/superset/explore_json/?*{"slice_id":${id}}*`;
@@ -72,7 +85,7 @@ describe('Dashboard filter', () => {
 
     cy.get('.Select__control input[type=text]')
       .first()
-      .focus({ force: true })
+      .focus()
       .type('So', { force: true });
 
     cy.get('.Select__menu').first().contains('Create "So"');
@@ -81,7 +94,7 @@ describe('Dashboard filter', () => {
     // we refocus the input again here. The is not happening in real life.
     cy.get('.Select__control input[type=text]')
       .first()
-      .focus({ force: true })
+      .focus()
       .type('uth Asia{enter}', { force: true });
 
     // by default, need to click Apply button to apply filter
@@ -90,8 +103,10 @@ describe('Dashboard filter', () => {
     // wait again after applied filters
     cy.wait(aliases.filter(x => x !== getAlias(filterId))).then(requests => {
       requests.forEach(xhr => {
-        const requestFormData = xhr.request.body;
-        const requestParams = JSON.parse(requestFormData.get('form_data'));
+        const requestFormData = xhr.request.body as FormData;
+        const requestParams = JSON.parse(
+          requestFormData.get('form_data') as string,
+        );
         expect(requestParams.extra_filters[0]).deep.eq({
           col: 'region',
           op: 'in',
diff --git 
a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js 
b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
index 8f7069c..650d372 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js
@@ -21,7 +21,6 @@ import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
 
 describe('Dashboard save action', () => {
   let dashboardId;
-  let boxplotChartId;
 
   beforeEach(() => {
     cy.server();
@@ -32,10 +31,6 @@ describe('Dashboard save action', () => {
       const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
       const dashboard = bootstrapData.dashboard_data;
       dashboardId = dashboard.id;
-      boxplotChartId = dashboard.slices.find(
-        slice => slice.form_data.viz_type === 'box_plot',
-      ).slice_id;
-
       cy.route('POST', 
`/superset/copy_dash/${dashboardId}/`).as('copyRequest');
     });
 
diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/link.test.js 
b/superset-frontend/cypress-base/cypress/integration/explore/link.test.js
index e8bc56c..1d0ce87 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/link.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/explore/link.test.js
@@ -55,8 +55,6 @@ describe('Test explore links', () => {
     // explicitly wait for the url response
     cy.wait('@getShortUrl');
 
-    cy.wait(100);
-
     cy.get('#shorturl-popover [data-test="short-url"]')
       .invoke('text')
       .then(text => {
diff --git 
a/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js 
b/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js
index 43e9706..d876ef6 100644
--- a/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/sqllab/tabs.test.js
@@ -24,30 +24,28 @@ describe('SqlLab query tabs', () => {
   });
 
   it('allows you to create a tab', () => {
-    cy.get('#a11y-query-editor-tabs > ul > li').then(tabList => {
+    cy.get('.SqlEditorTabs > ul > li').then(tabList => {
       const initialTabCount = tabList.length;
-
       // add tab
-      cy.get('#a11y-query-editor-tabs > ul > li').last().click();
-
-      cy.get('#a11y-query-editor-tabs > ul > li').should(
-        'have.length',
-        initialTabCount + 1,
+      cy.get('.SqlEditorTabs > ul > li').last().click();
+      // wait until we find the new tab
+      cy.get(`.SqlEditorTabs > ul > li:eq(${initialTabCount - 1})`).contains(
+        'Untitled Query',
       );
     });
   });
 
   it('allows you to close a tab', () => {
-    cy.get('#a11y-query-editor-tabs > ul > li').then(tabListA => {
+    cy.get('.SqlEditorTabs > ul > li').then(tabListA => {
       const initialTabCount = tabListA.length;
 
       // open the tab dropdown to remove
-      cy.get('#a11y-query-editor-tabs > ul > li .dropdown-toggle').click();
+      cy.get('.SqlEditorTabs > ul > li .dropdown-toggle').click();
 
       // first item is close
-      cy.get('#a11y-query-editor-tabs .close-btn a').click();
+      cy.get('.SqlEditorTabs .close-btn a').click();
 
-      cy.get('#a11y-query-editor-tabs > ul > li').should(
+      cy.get('.SqlEditorTabs > ul > li').should(
         'have.length',
         initialTabCount - 1,
       );
diff --git a/superset-frontend/cypress-base/cypress/plugins/index.js 
b/superset-frontend/cypress-base/cypress/plugins/index.js
index adfeabe..4df323f 100644
--- a/superset-frontend/cypress-base/cypress/plugins/index.js
+++ b/superset-frontend/cypress-base/cypress/plugins/index.js
@@ -16,16 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
 // This function is called when a project is opened or re-opened (e.g. due to
 // the project's config changing)
 
diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts 
b/superset-frontend/cypress-base/cypress/support/index.d.ts
new file mode 100644
index 0000000..80a936e
--- /dev/null
+++ b/superset-frontend/cypress-base/cypress/support/index.d.ts
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+// eslint-disable-next-line spaced-comment
+/// <reference types="cypress" />
+type JSONPrimitive = string | number | boolean | null;
+type JSONValue = JSONPrimitive | JSONObject | JSONArray;
+type JSONObject = { [member: string]: JSONValue };
+type JSONArray = JSONValue[];
+
+declare namespace Cypress {
+  interface Chainable {
+    /**
+     * Login test user.
+     */
+    login(): void;
+
+    /**
+     * Verify a waitXHR response and parse response JSON.
+     */
+    verifyResponseCodes(
+      xhr: WaitXHR,
+      callback?: (result: JSONValue) => void,
+    ): cy;
+
+    /**
+     * Verify slice container renders.
+     */
+    verifySliceContainer(chartSelector: JQuery.Selector): cy;
+
+    /**
+     * Verify slice successfully loaded.
+     */
+    verifySliceSuccess({
+      waitAlias,
+      querySubString,
+      chartSelector,
+    }: {
+      waitAlias: string;
+      querySubString: string;
+      chartSelector: JQuery.Selector;
+    }): cy;
+  }
+}
+
+declare module '@cypress/code-coverage/task';
diff --git a/superset-frontend/cypress-base/cypress/support/index.js 
b/superset-frontend/cypress-base/cypress/support/index.js
deleted file mode 100644
index 52bd671..0000000
--- a/superset-frontend/cypress-base/cypress/support/index.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-import '@cypress/code-coverage/support';
-import './commands';
-
-// The following is a workaround for Cypress not supporting fetch.
-// By setting window.fetch = null, we force the fetch polyfill to fall back
-// to xhr as described here https://github.com/cypress-io/cypress/issues/95
-Cypress.on('window:before:load', win => {
-  win.fetch = null; // eslint-disable-line no-param-reassign
-});
diff --git a/superset-frontend/cypress-base/cypress/support/commands.js 
b/superset-frontend/cypress-base/cypress/support/index.ts
similarity index 52%
rename from superset-frontend/cypress-base/cypress/support/commands.js
rename to superset-frontend/cypress-base/cypress/support/index.ts
index fafff6c..ad70b73 100644
--- a/superset-frontend/cypress-base/cypress/support/commands.js
+++ b/superset-frontend/cypress-base/cypress/support/index.ts
@@ -16,32 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-//
-//
-// -- This is a parent command --
-// Cypress.Commands.add("login", (email, password) => { ... })
-//
-//
-// -- This is a child command --
-// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) 
=> { ... })
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, 
options) => { ... })
-//
-//
-// -- This is will overwrite an existing command --
-// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
-
+import '@cypress/code-coverage/support';
 import readResponseBlob from '../utils/readResponseBlob';
 
 const BASE_EXPLORE_URL = '/superset/explore/?form_data=';
@@ -63,47 +38,60 @@ Cypress.Commands.add('visitChartByName', name => {
 });
 
 Cypress.Commands.add('visitChartById', chartId => {
-  cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${chartId}}`);
+  return cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${chartId}}`);
 });
 
 Cypress.Commands.add('visitChartByParams', params => {
-  cy.visit(`${BASE_EXPLORE_URL}${params}`);
+  return cy.visit(`${BASE_EXPLORE_URL}${params}`);
 });
 
-Cypress.Commands.add('verifyResponseCodes', async xhr => {
+Cypress.Commands.add('verifyResponseCodes', (xhr: XMLHttpRequest, callback) => 
{
   // After a wait response check for valid response
   expect(xhr.status).to.eq(200);
-
-  const responseBody = await readResponseBlob(xhr.response.body);
-
-  if (responseBody.error) {
-    expect(responseBody.error).to.eq(null);
-  }
+  readResponseBlob(xhr.response.body).then(res => {
+    expect(res).to.not.be.instanceOf(Error);
+    if (callback) {
+      callback(res);
+    }
+  });
+  return cy;
 });
 
 Cypress.Commands.add('verifySliceContainer', chartSelector => {
   // After a wait response check for valid slice container
-  cy.get('.slice_container').within(async () => {
+  cy.get('.slice_container').within(() => {
     if (chartSelector) {
-      const chart = await cy.get(chartSelector);
-      expect(chart[0].clientWidth).greaterThan(0);
-      expect(chart[0].clientHeight).greaterThan(0);
+      cy.get(chartSelector).then(chart => {
+        expect(chart[0].clientWidth).greaterThan(0);
+        expect(chart[0].clientHeight).greaterThan(0);
+      });
     }
   });
+  return cy;
 });
 
 Cypress.Commands.add(
   'verifySliceSuccess',
-  ({ waitAlias, querySubstring, chartSelector }) => {
-    cy.wait(waitAlias).then(async xhr => {
-      cy.verifyResponseCodes(xhr);
-
-      const responseBody = await readResponseBlob(xhr.response.body);
-      if (querySubstring) {
-        expect(responseBody.query).contains(querySubstring);
-      }
-
+  ({
+    waitAlias,
+    querySubstring,
+    chartSelector,
+  }: {
+    waitAlias: string;
+    querySubstring: string;
+    chartSelector: JQuery.Selector;
+  }) => {
+    cy.wait(waitAlias).then(xhr => {
       cy.verifySliceContainer(chartSelector);
+      cy.verifyResponseCodes(xhr, responseBody => {
+        if (querySubstring) {
+          type QueryResponse = { query: string };
+          expect(
+            responseBody && (responseBody as QueryResponse).query,
+          ).contains(querySubstring);
+        }
+      });
     });
+    return cy;
   },
 );
diff --git a/superset-frontend/cypress-base/cypress/utils/readResponseBlob.js 
b/superset-frontend/cypress-base/cypress/utils/readResponseBlob.ts
similarity index 63%
rename from superset-frontend/cypress-base/cypress/utils/readResponseBlob.js
rename to superset-frontend/cypress-base/cypress/utils/readResponseBlob.ts
index 6cf077b..560a5c5 100644
--- a/superset-frontend/cypress-base/cypress/utils/readResponseBlob.js
+++ b/superset-frontend/cypress-base/cypress/utils/readResponseBlob.ts
@@ -16,14 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// This function returns a promise that resolves to the value
-// of the passed response blob. It assumes the blob should be read as text,
-// and that the response can be parsed as JSON. This is needed to read
-// the value of any fetch-based response.
-export default function readResponseBlob(blob) {
-  return new Promise(resolve => {
-    const reader = new FileReader();
-    reader.onload = () => resolve(JSON.parse(reader.result));
-    reader.readAsText(blob);
+/**
+ * Read XHR response and parse it as JSON.
+ */
+export default function readResponseBlob(blob: Blob | JSONValue) {
+  return new Promise<ReturnType<JSON['parse']>>(resolve => {
+    if (blob instanceof Blob) {
+      const reader = new FileReader();
+      reader.onload = () => resolve(JSON.parse(String(reader.result || '')));
+      reader.readAsText(blob);
+    } else {
+      resolve(blob);
+    }
   });
 }
diff --git a/superset-frontend/cypress-base/tsconfig.json 
b/superset-frontend/cypress-base/tsconfig.json
new file mode 100644
index 0000000..eec99e4
--- /dev/null
+++ b/superset-frontend/cypress-base/tsconfig.json
@@ -0,0 +1,12 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "target": "ES5",
+    "lib": ["ES5", "ES2015", "DOM"],
+    "types": ["cypress"],
+    "allowJs": true,
+    "noEmit": true
+  },
+  "files": ["cypress/support/index.d.ts"],
+  "include": ["node_modules/cypress", "cypress/**/*.ts"]
+}

Reply via email to