Skip to content

Commit af0f186

Browse files
committed
Update sidepanel testsuite to adapt React 18+
1 parent e487b17 commit af0f186

File tree

11 files changed

+259
-126
lines changed

11 files changed

+259
-126
lines changed

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/jest.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ module.exports = {
2828
// Use identity-obj-proxy to load css and less files in tests.
2929
"moduleNameMapper": {
3030
"\\.(css|less)$": "identity-obj-proxy"
31-
}
31+
},
32+
"testEnvironment": "jsdom",
33+
"setupFilesAfterEnv": ['<rootDir>/jest.setup.js']
3234
}

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@
4747
"@jupyterlab/launcher": "^4.3.6",
4848
"@jupyterlab/mainmenu": "^4.3.6",
4949
"@lumino/widgets": "^2.2.1",
50+
"@rmwc/base": "^14.0.0",
5051
"@rmwc/button": "^8.0.6",
5152
"@rmwc/data-table": "^8.0.6",
5253
"@rmwc/dialog": "^8.0.6",
5354
"@rmwc/drawer": "^8.0.6",
5455
"@rmwc/fab": "^8.0.6",
5556
"@rmwc/list": "^8.0.6",
57+
"@rmwc/ripple": "^14.0.0",
5658
"@rmwc/textfield": "^8.0.6",
5759
"@rmwc/tooltip": "^8.0.6",
5860
"@rmwc/top-app-bar": "^8.0.6",
@@ -62,6 +64,9 @@
6264
},
6365
"devDependencies": {
6466
"@jupyterlab/builder": "^4.3.6",
67+
"@testing-library/dom": "^9.3.0",
68+
"@testing-library/jest-dom": "^6.1.4",
69+
"@testing-library/react": "^14.0.0",
6570
"@types/jest": "^29.5.14",
6671
"@types/react": "^18.2.0",
6772
"@types/react-dom": "^18.2.0",
@@ -73,6 +78,7 @@
7378
"eslint-plugin-react": "^7.33.2",
7479
"identity-obj-proxy": "^3.0.0",
7580
"jest": "^29.7.0",
81+
"jest-environment-jsdom": "^29.0.0",
7682
"npm-run-all": "^4.1.5",
7783
"prettier": "^3.2.4",
7884
"rimraf": "^5.0.5",

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/clusters/Clusters.test.tsx

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,27 @@ beforeEach(() => {
2626
root = createRoot(container);
2727
});
2828

29-
afterEach(() => {
30-
root.unmount();
31-
container.remove();
32-
container = null;
29+
afterEach(async () => {
30+
try {
31+
if (root) {
32+
await act(async () => {
33+
root.unmount();
34+
await new Promise(resolve => setTimeout(resolve, 0));
35+
});
36+
}
37+
} catch (error) {
38+
console.warn('During unmount:', error);
39+
} finally {
40+
if (container?.parentNode) {
41+
container.remove();
42+
}
43+
container = null;
44+
root = null;
45+
}
3346
});
34-
35-
it('renders info message about no clusters being available', () => {
47+
it('renders info message about no clusters being available', async () => {
3648
const clustersRef: React.RefObject<Clusters> = React.createRef<Clusters>();
37-
act(() => {
49+
await act(async () => {
3850
root.render(<Clusters sessionContext={{} as any} ref={clustersRef} />);
3951
const clusters = clustersRef.current;
4052
if (clusters) {
@@ -46,7 +58,7 @@ it('renders info message about no clusters being available', () => {
4658
expect(infoElement.textContent).toBe('No clusters detected.');
4759
});
4860

49-
it('renders a data-table', () => {
61+
it('renders a data-table', async () => {
5062
const clustersRef: React.RefObject<Clusters> = React.createRef<Clusters>();
5163
const testData = {
5264
key: {
@@ -58,13 +70,17 @@ it('renders a data-table', () => {
5870
dashboard: 'test-dashboard'
5971
}
6072
};
61-
act(() => {
62-
root.render(<Clusters sessionContext={{} as any} ref={clustersRef} />);
63-
const clusters = clustersRef.current;
64-
if (clusters) {
65-
clusters.setState({ clusters: testData });
66-
}
73+
await act(async () => {
74+
root.render(
75+
<Clusters sessionContext={{} as any} ref={clustersRef} />
76+
);
6777
});
78+
79+
await act(async () => {
80+
clustersRef.current?.setState({ clusters: testData });
81+
await new Promise(resolve => setTimeout(resolve, 100));
82+
});
83+
6884
const topAppBarHeader: Element = container.firstElementChild;
6985
expect(topAppBarHeader.tagName).toBe('HEADER');
7086
expect(topAppBarHeader.getAttribute('class')).toContain('mdc-top-app-bar');
@@ -92,7 +108,7 @@ it('renders a data-table', () => {
92108
const dataTableDiv: Element = clustersComponent.children[0];
93109
expect(dataTableDiv.tagName).toBe('DIV');
94110
expect(dataTableDiv.getAttribute('class')).toContain('mdc-data-table');
95-
const dataTable: Element = dataTableDiv.children[0];
111+
const dataTable: Element = dataTableDiv.children[0].firstElementChild;
96112
expect(dataTable.tagName).toBe('TABLE');
97113
expect(dataTable.getAttribute('class')).toContain('mdc-data-table__table');
98114
const dataTableHead: Element = dataTable.children[0];

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/common/HtmlView.test.tsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,42 @@ beforeEach(() => {
3030
root = createRoot(container);
3131
});
3232

33-
afterEach(() => {
34-
root.unmount();
35-
container.remove();
36-
container = null;
37-
jest.clearAllMocks();
33+
afterEach(async () => {
34+
try {
35+
if (root) {
36+
await act(async () => {
37+
root.unmount();
38+
await new Promise(resolve => setTimeout(resolve, 0));
39+
});
40+
}
41+
} catch (error) {
42+
console.warn('During unmount:', error);
43+
} finally {
44+
if (container?.parentNode) {
45+
container.remove();
46+
}
47+
jest.clearAllMocks();
48+
container = null;
49+
root = null;
50+
}
3851
});
3952

4053
describe('HtmlView', () => {
41-
it('renders provided html', () => {
54+
it('renders provided html', async () => {
4255
const htmlViewRef: React.RefObject<HtmlView> = React.createRef<HtmlView>();
4356
const spiedConsole = jest.spyOn(console, 'log');
4457
const fakeHtmlProvider = {
4558
html: '<div>Test</div>',
4659
script: ['console.log(1);', 'console.log(2);']
4760
} as IHtmlProvider;
48-
act(() => {
61+
await act(async () => {
4962
root.render(
5063
<HtmlView ref={htmlViewRef} htmlProvider={fakeHtmlProvider} />
5164
);
52-
const htmlView = htmlViewRef.current;
53-
if (htmlView) {
54-
htmlView.updateRender();
55-
}
65+
});
66+
await act(async () => {
67+
htmlViewRef.current?.updateRender();
68+
await new Promise(resolve => setTimeout(resolve, 100));
5669
});
5770
const htmlViewElement: Element = container.firstElementChild;
5871
expect(htmlViewElement.tagName).toBe('DIV');
@@ -64,28 +77,28 @@ describe('HtmlView', () => {
6477

6578
it(
6679
'only executes incrementally updated Javascript ' +
67-
'as html provider updated',
68-
() => {
80+
'as html provider updated',
81+
async () => {
6982
const htmlViewRef: React.RefObject<HtmlView> =
7083
React.createRef<HtmlView>();
7184
const spiedConsole = jest.spyOn(console, 'log');
7285
const fakeHtmlProvider = {
7386
html: '<div></div>',
7487
script: ['console.log(1);']
7588
} as IHtmlProvider;
76-
act(() => {
89+
await act(async () => {
7790
root.render(
7891
<HtmlView ref={htmlViewRef} htmlProvider={fakeHtmlProvider} />
7992
);
80-
const htmlView = htmlViewRef.current;
81-
if (htmlView) {
82-
htmlView.updateRender();
83-
}
93+
});
94+
await act(async () => {
95+
htmlViewRef.current?.updateRender();
96+
await new Promise(resolve => setTimeout(resolve, 100));
8497
});
8598
expect(spiedConsole).toHaveBeenCalledWith(1);
8699
expect(spiedConsole).toHaveBeenCalledTimes(1);
87100
fakeHtmlProvider.script.push('console.log(2);');
88-
act(() => {
101+
await act(async () => {
89102
const htmlView = htmlViewRef.current;
90103
if (htmlView) {
91104
htmlView.updateRender();
@@ -97,8 +110,8 @@ describe('HtmlView', () => {
97110
);
98111
});
99112
describe('Function importHtml', () => {
100-
it('imports webcomponents script', () => {
101-
act(() => {
113+
it('imports webcomponents script', async () => {
114+
await act(async () => {
102115
importHtml([]);
103116
});
104117
const scriptElement: Element = document.head.firstElementChild;

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableList.test.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,27 @@ beforeEach(() => {
3030
root = createRoot(container);
3131
});
3232

33-
afterEach(() => {
34-
root.unmount();
35-
container.remove();
36-
container = null;
33+
afterEach(async () => {
34+
try {
35+
if (root) {
36+
await act(async () => {
37+
root.unmount();
38+
await new Promise(resolve => setTimeout(resolve, 0));
39+
});
40+
}
41+
} catch (error) {
42+
console.warn('During unmount:', error);
43+
} finally {
44+
if (container?.parentNode) {
45+
container.remove();
46+
}
47+
container = null;
48+
root = null;
49+
}
3750
});
3851

39-
it('renders a list', () => {
40-
act(() => {
52+
it('renders a list', async () => {
53+
await act(async () => {
4154
root.render(
4255
<InspectableList
4356
inspectableViewModel={mockedInspectableViewModel as any}
@@ -71,14 +84,14 @@ it('renders a list', () => {
7184
const listHandleItem: Element = listHandle.firstElementChild;
7285
expect(listHandleItem.tagName).toBe('LI');
7386
expect(listHandleItem.getAttribute('class')).toContain('mdc-list-item');
74-
const listHandleText: Element = listHandleItem.firstElementChild;
87+
const listHandleText: Element = listHandleItem.children[2];
7588
expect(listHandleText.getAttribute('class')).toContain('mdc-list-item__text');
7689
const listHandlePrimaryText: Element = listHandleText.firstElementChild;
7790
expect(listHandlePrimaryText.getAttribute('class')).toContain(
7891
'mdc-list-item__primary-text'
7992
);
8093
expect(listHandlePrimaryText.textContent).toBe('pipeline_name');
81-
const listHandleMetaIcon: Element = listHandleItem.children[1];
94+
const listHandleMetaIcon: Element = listHandleItem.children[3];
8295
expect(listHandleMetaIcon.tagName).toBe('I');
8396
expect(listHandleMetaIcon.getAttribute('class')).toContain(
8497
'mdc-list-item__meta'

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableListItem.test.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,27 @@ beforeEach(() => {
2626
root = createRoot(container);
2727
});
2828

29-
afterEach(() => {
30-
root.unmount();
31-
container.remove();
32-
container = null;
29+
afterEach(async () => {
30+
try {
31+
if (root) {
32+
await act(async () => {
33+
root.unmount();
34+
await new Promise(resolve => setTimeout(resolve, 0));
35+
});
36+
}
37+
} catch (error) {
38+
console.warn('During unmount:', error);
39+
} finally {
40+
if (container?.parentNode) {
41+
container.remove();
42+
}
43+
container = null;
44+
root = null;
45+
}
3346
});
3447

35-
it('renders an item', () => {
36-
act(() => {
48+
it('renders an item', async () => {
49+
await act(async () => {
3750
root.render(
3851
<InspectableListItem
3952
id="id"
@@ -48,7 +61,7 @@ it('renders an item', () => {
4861
const liElement: Element = container.firstElementChild;
4962
expect(liElement.tagName).toBe('LI');
5063
expect(liElement.getAttribute('class')).toBe('mdc-list-item');
51-
const textElement: Element = liElement.firstElementChild;
64+
const textElement: Element = liElement.children[1];
5265
expect(textElement.getAttribute('class')).toBe('mdc-list-item__text');
5366
const primaryTextElement: Element = textElement.firstElementChild;
5467
expect(primaryTextElement.getAttribute('class')).toBe(

sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/src/__tests__/inspector/InspectableView.test.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,34 @@ beforeEach(() => {
3131
root = createRoot(container);
3232
});
3333

34-
afterEach(() => {
35-
root.unmount();
36-
container.remove();
37-
container = null;
34+
afterEach(async () => {
35+
try {
36+
if (root) {
37+
await act(async () => {
38+
root.unmount();
39+
await new Promise(resolve => setTimeout(resolve, 0));
40+
});
41+
}
42+
} catch (error) {
43+
console.warn('During unmount:', error);
44+
} finally {
45+
if (container?.parentNode) {
46+
container.remove();
47+
}
48+
container = null;
49+
root = null;
50+
}
3851
});
3952

40-
it('does not render options if inspecting a pipeline', () => {
53+
it('does not render options if inspecting a pipeline', async () => {
4154
const fakeModel = {
4255
html: '',
4356
script: [] as string[],
4457
inspectableType: 'pipeline',
4558
identifier: 'id',
4659
options: {} as IOptions
4760
} as InspectableViewModel;
48-
act(() => {
61+
await act(async () => {
4962
root.render(<InspectableView model={fakeModel} />);
5063
});
5164
const inspectableViewElement: Element = container.firstElementChild;
@@ -54,7 +67,7 @@ it('does not render options if inspecting a pipeline', () => {
5467
expect(optionsElement.innerHTML).toBe('<span></span>');
5568
});
5669

57-
it('renders options if inspecting a pcollection', () => {
70+
it('renders options if inspecting a pcollection', async () => {
5871
const inspectableViewRef: React.RefObject<InspectableView> =
5972
React.createRef<InspectableView>();
6073
const fakeModel = {
@@ -67,7 +80,7 @@ it('renders options if inspecting a pcollection', () => {
6780
visualizeInFacets: true
6881
} as IOptions
6982
} as InspectableViewModel;
70-
act(() => {
83+
await act(async () => {
7184
root.render(<InspectableView ref={inspectableViewRef} model={fakeModel} />);
7285
const inspectableView = inspectableViewRef.current;
7386
if (inspectableView) {
@@ -106,15 +119,15 @@ it('renders options if inspecting a pcollection', () => {
106119
);
107120
});
108121

109-
it('renders an html view', () => {
122+
it('renders an html view', async () => {
110123
const fakeModel = {
111124
html: '<div>fake html</div>',
112125
script: ['console.log(1)'],
113126
inspectableType: 'pcollection',
114127
identifier: 'id',
115128
options: {} as IOptions
116129
} as InspectableViewModel;
117-
act(() => {
130+
await act(async () => {
118131
root.render(<InspectableView model={fakeModel} />);
119132
});
120133
const inspectableViewElement: Element = container.firstElementChild;

0 commit comments

Comments
 (0)