Skip to content

[RW-5959][risk=no] Upgrade jest to latest version #4733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 1, 2021
Merged
16 changes: 9 additions & 7 deletions e2e/app/page/notebook-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ enum CssSelector {
}

enum Xpath {
fileMenuDropdown = '//a[text()="File"]',
downloadMenuDropdown = '//a[text()="Download as"]',
downloadIpynbButton = '//*[@id="download_script"]/a',
downloadMarkdownButton = '//*[@id="download_markdown"]/a'
fileMenuDropdown = './/a[text()="File"]',
downloadMenuDropdown = './/a[text()="Download as"]',
downloadIpynbButton = './/*[@id="download_script"]/a',
downloadMarkdownButton = './/*[@id="download_markdown"]/a'
}

export enum Mode {
Expand Down Expand Up @@ -94,9 +94,11 @@ export default class NotebookPage extends AuthenticatedPage {
private async downloadAs(formatXpath: string): Promise<NotebookDownloadModal> {
const frame = await this.getIFrame();

await (await frame.waitForXPath(Xpath.fileMenuDropdown, { visible: true })).click();
await (await frame.waitForXPath(Xpath.downloadMenuDropdown, { visible: true })).hover();
await (await frame.waitForXPath(formatXpath, { visible: true })).click();
await frame.waitForXPath(Xpath.fileMenuDropdown, { visible: true }).then((element) => element.click());
await frame.waitForXPath(Xpath.downloadMenuDropdown, { visible: true }).then((element) => element.hover());
const menuOption = await frame.waitForXPath(formatXpath, { visible: true });
await menuOption.hover();
await menuOption.click();

const modal = new NotebookDownloadModal(this.page, frame);
return await modal.waitForLoad();
Expand Down
4 changes: 3 additions & 1 deletion e2e/jest.test-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ beforeEach(async () => {
const title = await getPageTitle();
try {
const args = await Promise.all(message.args().map((a) => describeJsHandle(a)));
console[message.type() === 'warning' ? 'warn' : message.type()](`Page console: "${title}"\n`, ...args);
const msgType = message.type() === 'warning' ? 'warn' : message.type();
console[msgType](`Page console: "${title}"`);
console[msgType](...args);
console.log('');
} catch (err) {
console.error(`❗ "${title}"\nException occurred when getting page console message.\n${err}\n${message.text()}`);
Expand Down
24 changes: 14 additions & 10 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"dev-up": "yarn && yarn run codegen && yarn start",
"build": "ng build",
"test": "ng test --source-map=true",
"test-react": "jest",
"test-react": "yarn jest",
"lint": "yarn eslint src --ext .ts,.tsx && ng lint --type-check=true",
"e2e": "ng e2e",
"codegen": "yarn run codegen-clean && yarn run codegen-swagger && yarn run codegen-swagger && yarn run notebooks-codegen-swagger",
Expand All @@ -18,31 +18,35 @@
"coverage": "ng test --source-map=false --watch=false --code-coverage"
},
"jest": {
"preset": "ts-jest",
"clearMocks": true,
"setupFiles": [
"./test-shim.js",
"./test-setup.js"
],
"setupTestFrameworkScriptFile": "./test-setup-script.js",
"setupFilesAfterEnv": [
"./test-setup-script.js"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"spec.ts"
"json",
"node"
],
"moduleDirectories": [
"node_modules",
"src"
],
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
"^.+\\.tsx?$": "ts-jest"
},
"testMatch": [
"**/*.spec.tsx"
],
"globals": {
"ts-jest": {
"tsConfig": "src/tsconfig.jest.json"
"tsconfig": "<rootDir>/src/tsconfig.jest.json"
}
}
},
Expand Down Expand Up @@ -117,7 +121,6 @@
"serialize-javascript": "^3.1.0",
"stackdriver-errors-js": "^0.4.0",
"stacktrace-js": "^2.0.0",
"ts-jest": "^25.3.0",
"ts-key-enum": "^3.0.0",
"tslib": "^1.9.0",
"validate.js": "^0.12.0",
Expand All @@ -136,16 +139,16 @@
"@types/google.analytics": "0.0.39",
"@types/jasmine": "^3.3.5",
"@types/jasminewd2": "^2.0.3",
"@types/jest": "^23.3.11",
"@types/jest": "^26.0.21",
"@types/node": "^10.12.11",
"@types/react-router-dom": "^5.1.5",
"@typescript-eslint/eslint-plugin": "^4.15.2",
"@typescript-eslint/parser": "^4.15.2",
"codelyzer": "^4.1.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"enzyme-adapter-react-16": "^1.15.6",
"eslint": "^6.8.0",
"eslint-plugin-jest": "^23.20.0",
"eslint-plugin-jest": "^24.3.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prefer-arrow": "^1.2.0",
"eslint-plugin-prettier": "^3.1.3",
Expand All @@ -154,7 +157,7 @@
"eslint-plugin-sort-keys-fix": "^1.1.0",
"jasmine-core": "~3.3.0",
"jasmine-spec-reporter": "~4.2.1",
"jest": "^23.6.0",
"jest": "^26.6.3",
"karma": "^5.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~1.0.1",
Expand All @@ -167,6 +170,7 @@
"prettier": "^2.0.4",
"react-test-renderer": "^16.6.3",
"set-value": "^2.0.1",
"ts-jest": "^26.5.4",
"ts-node": "~3.0.4",
"tslint": "^5.9.1",
"tslint-eslint-rules": "^5.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export const CohortSearch = withCurrentCohortSearchContext()(class extends React
clearTimeout(this.growlTimer);
}
// This is to set style display: 'none' on the growl so it doesn't block the nav icons in the sidebar
this.growlTimer = setTimeout(() => this.setState({growlVisible: false}), 2500);
this.growlTimer = global.setTimeout(() => this.setState({growlVisible: false}), 2500);
Copy link
Contributor Author

@aweng98 aweng98 Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolving typescript issue

this.setState({growlVisible: true});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export const SearchGroupItem = withCurrentWorkspace()(
remove() {
triggerEvent('Delete', 'Click', 'Snowman - Delete Criteria - Cohort Builder');
this.updateSearchRequest('status', 'pending', true);
const timeout = setTimeout(() => {
const timeout: Timeout = global.setTimeout(() => {
this.updateSearchRequest(null, null, false, true);
}, 10000);
this.setState({timeout});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ export const SearchGroup = withCurrentWorkspace()(
remove() {
triggerEvent('Delete', 'Click', 'Snowman - Delete Group - Cohort Builder');
this.hide('pending');
this.deleteTimeout = setTimeout(() => {
this.deleteTimeout = global.setTimeout(() => {
this.removeGroup();
}, 10000);
}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/pages/analysis/notebook-redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export const NotebookRedirect = fp.flow(
this.incrementProgress(Progress.Redirecting);

// give it a second to "redirect"
this.redirectTimer = setTimeout(() => this.incrementProgress(Progress.Loaded), redirectMillis);
this.redirectTimer = global.setTimeout(() => this.incrementProgress(Progress.Loaded), redirectMillis);
}

private async getNotebookPathAndLocalize(runtime: Runtime) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const AnnotationItem = fp.flow(
console.error(error);
this.setState({saving: false, error: true});
} finally {
const timeout = setTimeout(() => this.setState({error: false, success: false}), 5000);
const timeout: Timeout = global.setTimeout(() => this.setState({error: false, success: false}), 5000);
this.setState({savingValue: undefined, timeout});
}
}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/pages/data/criteria-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ export const CriteriaSearch = fp.flow(withUrlParams(), withCurrentWorkspace())(c
clearTimeout(this.growlTimer);
}
// This is to set style display: 'none' on the growl so it doesn't block the nav icons in the sidebar
this.growlTimer = setTimeout(() => this.setState({growlVisible: false}), 2500);
this.growlTimer = global.setTimeout(() => this.setState({growlVisible: false}), 2500);
this.setState({growlVisible: true});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export class AccountCreation extends React.Component<AccountCreationProps, Accou
// TODO: This should use a debounce, rather than manual setTimeout()s.
clearTimeout(this.usernameCheckTimeout);
this.setState({usernameCheckInProgress: true, usernameConflictError: false});
this.usernameCheckTimeout = setTimeout(() => {
this.usernameCheckTimeout = global.setTimeout(() => {
profileApi().isUsernameTaken(username)
.then((body) => {
this.setState({usernameCheckInProgress: false, usernameConflictError: body.isTaken});
Expand Down
5 changes: 4 additions & 1 deletion ui/src/app/pages/profile/profile-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,10 @@ export const ProfilePage = fp.flow(

const makeProfileInput = ({title, valueKey, isLong = false, ...props}) => {
let errorText = profile && errors && errors[valueKey];
if (valueKey && Array.isArray(valueKey) && valueKey.length > 1) {
if (valueKey && !Array.isArray(valueKey)) {
valueKey = [valueKey];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petesantos can you review this specific change? Based on my reading of the previous code, L444 when the valueKey is 'givenName', the array expansion should equate to:

this.setState(fp.set(['currentProfile', 'g', 'i', 'v', 'e', 'n', 'N', 'a', 'm', 'e'], v)) 

With Alex's change, the test started failing from this somehow. Do you have ideas on what's going on here? I'm still puzzled as to how the current code is functional.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting, it looks like JS and TS handle spread differently. When the TS compiles down to javascript it must do something to prevent strings from getting split apart on a spread. The new code looks more correct and in-line with what I would expect. I will put together a code example demonstrating this issue. What was the specific error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI unit profile-page test failure:

FAIL src/app/pages/profile/profile-page.spec.tsx
  ● ProfilePageComponent › should invalidate inputs correctly

    expect(received).toBeTruthy()

    Received: false

      88 |     const wrapper = component();
      89 |     wrapper.find(TextInput).first().simulate('change', {target: {value: ''}});
    > 90 |     expect(wrapper.find(TextInput).first().prop('invalid')).toBeTruthy();
         |                                                             ^
      91 |   });
      92 | 
      93 |   it('should display/update address', async() => {

      at Object.<anonymous> (src/app/pages/profile/profile-page.spec.tsx:90:61)

Copy link
Contributor

@calbach calbach Mar 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks - it should be possible to set up a more isolated reproducer here (the above failure is highly detached from the cause, and required some trial and error to trace down), I can try and help set this up as well if you'd like.

For the purposes of this PR I think it should be fine to merge now. Pete's comment of "The new code looks more correct and in-line with what I would expect" answers what I was looking for, which is to make sure I wasn't missing something obvious and that the change makes sense.

Copy link
Contributor

@petesantos petesantos Apr 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the issue:

This Typescript code (in version 3.2.z)

const checkValueKey = (valueKey) => {
  console.log(['test', ...valueKey])
}

checkValueKey('test')

compiles to this in javascript:

var checkValueKey = function (valueKey) {
    console.log(['test'].concat(valueKey));
};
checkValueKey('test');

When using concat as a spread operator you will see the behavior we are seeing in the code. The string item in the concat will not be split apart.

However, the same code when compiled in TS 4.2.3 yields:

var __spreadArray = (this && this.__spreadArray) || function (to, from) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
var checkValueKey = function (valueKey) {
    console.log(__spreadArray(['test'], valueKey));
};
checkValueKey('test');

Where the __spreadArray function more closely follows what JS does with the spread operator, splitting apart the string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find! Thanks. That seems like the likely culprit.

Something that's still not obvious to me though is why the typescript version changed with this PR, that seems like an unexpected side effect. My best guess is that changing ts-jest changed the Typescript version behind the scenes for tests, but if so this interaction is not obvious from yarn.lock or from the ts-jest docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like "ts-jest": "^26.5.4" uses TypeScript 4.x. This is probably why this error cropped up in the test.

This is a caveat we should be aware of - the jest tests will be using a different version of typescript from our code.

Copy link
Contributor

@petesantos petesantos Apr 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more update - I was wondering what our ES target version was, and whether a change to that would prevent substituting the spread operator at all, I don't think there is any browser we are targeting that doesn't support this.
Looking at the tsconfig.json L13 we are changing our ES target to es6 in this change.
Prior to this change, the code in question would compile to this (note the concat) :

return _this.setState(lodash_fp__WEBPACK_IMPORTED_MODULE_1__["set"](['currentProfile'].concat(valueKey), v));

and it now compiles to:

onChange: v => this.setState(lodash_fp__WEBPACK_IMPORTED_MODULE_1__["set"](['currentProfile', ...valueKey], v)),

I am curious as to whether the error popped up as a result of the target change, or the jest upgrade. In other words - is jest using a different typescript version from our build?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright... Final update... just for a full understanding of what is happening.
It looks like ts-jest is using our version of TS, the real issue is with the target change in the config and how it effects the resulting Javascript that comes out.

This is what I tested to confirm the behavior of the tests:

ts-jest version  | target | result
----------------------------------------------
25.3.0           | es5    |  pass (because it uses concat )
25.3.0           | es6    |  fail (uses spread operator)
26.5.4           | es5    |  pass (uses concat) --> If this were using 4.x of TS it would fail by using `__spreadArray`
26.5.4           | es6    |  fail (uses spread operator)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% confident but it looks to me that there was one usage of valueKey that this missed:
valueKey: 'verifiedInstitutionalAffiliation.institutionDisplayName' -> #4822

}
if (valueKey && valueKey.length > 1) {
Comment on lines +436 to +439
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profile-page.spec.tsx was failing consistently after versions upgrade. @ch helped me and figured out it was caused by a bug here.

errorText = profile && errors && errors[valueKey[1]];
}
const inputProps = {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/utils/leo-runtime-initializer.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Runtime} from 'generated/fetch';
import {RuntimeStatus} from 'generated/fetch';
import {RuntimeApi} from 'generated/fetch/api';
import SpyInstance = jest.SpyInstance;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be combined with the import statement below it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I wasn't able to combine two imports.
However, I can do this so that we don't need import SpyInstance = jest.SpyInstance;

let mockGetRuntime: jest.SpyInstance;
let mockCreateRuntime: jest.SpyInstance;
let mockDeleteRuntime: jest.SpyInstance;
let mockStartRuntime: jest.SpyInstance;

Let me know if you like me to make this change.

import expect = jest.Expect;
import {expect} from '@jest/globals';
import {RuntimesApi as LeoRuntimesApi} from 'notebooks-generated/fetch';
import {defaultRuntime, RuntimeApiStub} from 'testing/stubs/runtime-api-stub';
import {LeoRuntimesApiStub} from 'testing/stubs/leo-runtimes-api-stub';
Expand Down
8 changes: 8 additions & 0 deletions ui/src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,11 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
* Need to import at least one locale-data with intl.
*/
// import 'intl/locale-data/jsonp/en';

/**
* https://github.com/angular/angular-cli/issues/9827#issuecomment-386154063
* Add global to window, assigning the value of window itself.
* Resolving ERROR: ReferenceError: global is not defined.
*
*/
(window as any).global = window;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puppeteer notebook tests were failing. Failures were caused by ReferenceError: global is not defined.
Screen Shot 2021-04-01 at 11 35 31 AM

4 changes: 1 addition & 3 deletions ui/src/tsconfig.jest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
// No outDir needs to be specified here, as test runs will not produce output files anyway.
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target: "es6" inherited from ../tsconfig.json.

"types": [
"jest",
"node"
],
"jsx": "react"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inherited from ../tsconfig.json

]
},
"files": [
"polyfills.ts"
Expand Down
5 changes: 1 addition & 4 deletions ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
Comment on lines -14 to -16
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defining typeRoots is unnecessary. It is part of the default value.
https://www.typescriptlang.org/tsconfig#typeRoots

"lib": [
"es2016",
"es2018.promise",
"dom"
],
"module": "es2015",
"baseUrl": "./",
"baseUrl": "./src",
"jsx": "react",
"resolveJsonModule": true
}
Expand Down
Loading