@hrforte/react-pivottable 2.2.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/__tests__/PivotTableUI-processing-test.js"],"names":["global","IS_REACT_ACT_ENVIRONMENT","records","name","gender","colour","trials","baseProps","data","rows","cols","aggregatorName","vals","rendererName","rerenderPivot","root","props","render","React","createElement","PivotTableUI","describe","containers","beforeEach","afterEach","forEach","container","unmount","node","remove","mount","document","body","appendChild","entry","push","it","expect","querySelector","toBeNull","not","Object","assign","showProcessing","overlay","textContent","toBe","PivotLoadingOverlay","wait","Promise","setTimeout","resolve","ms","actAsync","reject","callback","then","workerInstances","FakeWorker","url","posted","onmessage","message","previousWorker","Worker","workerThreshold","restore","worker","toBeDefined","computeMsg","find","m","type","result","id","catch","error"],"mappings":"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","file":"PivotTableUI-processing-test.js","sourcesContent":["import React, {act} from 'react';\nimport {createRoot} from 'react-dom/client';\nimport PivotTableUI, {PivotLoadingOverlay} from '../PivotTableUI';\nimport {\n computePivotState,\n createWorkerConfig,\n materializeData,\n} from '../pivotCompute';\n/* eslint-disable no-magic-numbers */\n\n// React 19 needs to be told that this environment supports `act`.\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\n// `showProcessing` is a debugging/testing hook that forces the loading\n// overlay on top of the rendered table without running a real worker\n// computation, so the overlay can be inspected while consuming the package.\nconst records = [\n {name: 'Nick', gender: 'male', colour: 'blue', trials: 103},\n {name: 'Jane', gender: 'female', colour: 'red', trials: 95},\n {name: 'John', gender: 'male', colour: 'blue', trials: 112},\n {name: 'Carol', gender: 'female', colour: 'yellow', trials: 102},\n];\n\nconst baseProps = {\n data: records,\n rows: ['name'],\n cols: [],\n aggregatorName: 'Count',\n vals: [],\n rendererName: 'Table',\n};\n\nfunction rerenderPivot(root, props) {\n act(() => {\n root.render(React.createElement(PivotTableUI, props));\n });\n}\n\ndescribe(' PivotTableUI showProcessing', function() {\n let containers;\n\n beforeEach(function() {\n containers = [];\n });\n\n afterEach(function() {\n containers.forEach(container => {\n act(() => {\n container.root.unmount();\n });\n container.node.remove();\n });\n });\n\n const mount = props => {\n const node = document.createElement('div');\n document.body.appendChild(node);\n const root = createRoot(node);\n act(() => {\n root.render(React.createElement(PivotTableUI, props));\n });\n const entry = {node, root};\n containers.push(entry);\n return entry;\n };\n\n it('does not render the overlay by default', function() {\n const {node} = mount(baseProps);\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n });\n\n it('renders the overlay on top of the table when showProcessing is true', function() {\n const {node, root} = mount(baseProps);\n // Table is rendered from the real data first (no overlay).\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n\n rerenderPivot(root, Object.assign({}, baseProps, {showProcessing: true}));\n\n const overlay = node.querySelector('.pvtLoadingOverlay');\n expect(overlay).not.toBeNull();\n // The overlay is layered on top of the table: the table must survive.\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n // Only a spinner and a label: progress percentages were removed, the\n // overlay simply stays until the worker posts its result event.\n expect(overlay.querySelector('.pvtLoadingSpinner')).not.toBeNull();\n expect(overlay.querySelector('.pvtLoadingText').textContent).toBe(\n 'Processing…'\n );\n expect(overlay.querySelector('.pvtLoadingBar')).toBeNull();\n expect(overlay.querySelector('.pvtLoadingPercent')).toBeNull();\n\n // Turning the flag back off removes the overlay.\n rerenderPivot(root, baseProps);\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n });\n\n it('renders a spinner without any percentage markup', function() {\n const node = document.createElement('div');\n act(() => {\n createRoot(node).render(\n React.createElement(PivotLoadingOverlay, null)\n );\n });\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n expect(node.querySelector('.pvtLoadingSpinner')).not.toBeNull();\n expect(node.querySelector('.pvtLoadingBarWrap')).toBeNull();\n expect(node.querySelector('.pvtLoadingPercent')).toBeNull();\n expect(node.querySelector('.pvtLoadingText').textContent).toBe(\n 'Processing…'\n );\n });\n\n it('hides the overlay only after the worker responds and the table renders', function() {\n const wait = ms => new Promise(resolve => setTimeout(resolve, ms));\n // React's act() returns a non-standard thenable whose .then() does not\n // return a promise, so it cannot be chained directly - wrap it first.\n const actAsync = callback =>\n new Promise((resolve, reject) => act(callback).then(resolve, reject));\n const workerInstances = [];\n class FakeWorker {\n constructor(url) {\n this.url = url;\n this.posted = [];\n this.onmessage = null;\n workerInstances.push(this);\n }\n\n postMessage(message) {\n this.posted.push(message);\n }\n\n terminate() {}\n }\n\n const previousWorker = global.Worker;\n global.Worker = FakeWorker;\n\n // workerThreshold: 2 makes this small dataset worker-eligible.\n const props = Object.assign({}, baseProps, {workerThreshold: 2});\n const {node} = mount(props);\n\n const restore = () => {\n global.Worker = previousWorker;\n };\n\n // The overlay is up while the computation is pending.\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n\n // Wait out the dispatch debounce so the request reaches the worker.\n return actAsync(() => wait(80))\n .then(() => {\n const worker = workerInstances[0];\n expect(worker).toBeDefined();\n const computeMsg = worker.posted.find(m => m.type === 'pivot:compute');\n expect(computeMsg).toBeDefined();\n // The worker has not answered yet: the overlay must stay up.\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n\n // Worker finishes and posts its result event.\n const result = computePivotState(\n materializeData(records, {}),\n createWorkerConfig(props)\n );\n return actAsync(() => {\n worker.onmessage({\n data: {type: 'pivot:result', id: computeMsg.id, result},\n });\n return wait(0);\n });\n })\n .then(() => {\n // The overlay is gone only now - and the freshly computed table is\n // already on screen in the same state where the overlay disappeared.\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n restore();\n })\n .catch(error => {\n restore();\n throw error;\n });\n });\n});\n"]}
1
+ {"version":3,"sources":["../../src/__tests__/PivotTableUI-processing-test.js"],"names":["global","IS_REACT_ACT_ENVIRONMENT","records","name","gender","colour","trials","baseProps","data","rows","cols","aggregatorName","vals","rendererName","rerenderPivot","root","props","render","React","createElement","PivotTableUI","describe","containers","beforeEach","afterEach","forEach","container","unmount","node","remove","mount","document","body","appendChild","entry","push","it","expect","querySelector","toBeNull","not","originalWorker","Worker","errorSpy","jest","spyOn","console","mockImplementation","Object","assign","Array","fill","map","_","index","warnings","mock","calls","call","join","filter","message","includes","toEqual","mockRestore","showProcessing","overlay","parentElement","classList","contains","toBe","textContent","PivotLoadingOverlay","wait","Promise","setTimeout","resolve","ms","actAsync","reject","callback","then","workerInstances","FakeWorker","url","posted","onmessage","previousWorker","workerThreshold","restore","WORKER_DEBOUNCE_MS","worker","toBeDefined","computeMsg","find","m","type","result","id","catch","error"],"mappings":"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","file":"PivotTableUI-processing-test.js","sourcesContent":["import React, {act} from 'react';\nimport {createRoot} from 'react-dom/client';\nimport PivotTableUI, {PivotLoadingOverlay} from '../PivotTableUI';\nimport {\n computePivotState,\n createWorkerConfig,\n materializeData,\n WORKER_DEBOUNCE_MS,\n} from '../pivotCompute';\n/* eslint-disable no-magic-numbers */\n\n// React 19 needs to be told that this environment supports `act`.\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\n// `showProcessing` is a debugging/testing hook that forces the loading\n// overlay on top of the rendered table without running a real worker\n// computation, so the overlay can be inspected while consuming the package.\nconst records = [\n {name: 'Nick', gender: 'male', colour: 'blue', trials: 103},\n {name: 'Jane', gender: 'female', colour: 'red', trials: 95},\n {name: 'John', gender: 'male', colour: 'blue', trials: 112},\n {name: 'Carol', gender: 'female', colour: 'yellow', trials: 102},\n];\n\nconst baseProps = {\n data: records,\n rows: ['name'],\n cols: [],\n aggregatorName: 'Count',\n vals: [],\n rendererName: 'Table',\n};\n\nfunction rerenderPivot(root, props) {\n act(() => {\n root.render(React.createElement(PivotTableUI, props));\n });\n}\n\ndescribe(' PivotTableUI showProcessing', function() {\n let containers;\n\n beforeEach(function() {\n containers = [];\n });\n\n afterEach(function() {\n containers.forEach(container => {\n act(() => {\n container.root.unmount();\n });\n container.node.remove();\n });\n });\n\n const mount = props => {\n const node = document.createElement('div');\n document.body.appendChild(node);\n const root = createRoot(node);\n act(() => {\n root.render(React.createElement(PivotTableUI, props));\n });\n const entry = {node, root};\n containers.push(entry);\n return entry;\n };\n\n it('does not render the overlay by default', function() {\n const {node} = mount(baseProps);\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n });\n\n it('does not pass undefined attrValues while the worker is pending', function() {\n const originalWorker = global.Worker;\n const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});\n global.Worker = class PendingWorker {\n postMessage() {}\n terminate() {}\n };\n try {\n mount(\n Object.assign({}, baseProps, {\n data: new Array(201).fill(null).map((_, index) => ({\n name: `Employee ${index}`,\n })),\n })\n );\n const warnings = errorSpy.mock.calls\n .map(call => call.join(' '))\n .filter(message => message.includes('attrValues'));\n expect(warnings).toEqual([]);\n } finally {\n global.Worker = originalWorker;\n errorSpy.mockRestore();\n }\n });\n\n it('renders the overlay on top of the table when showProcessing is true', function() {\n const {node, root} = mount(baseProps);\n // Table is rendered from the real data first (no overlay).\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n\n rerenderPivot(root, Object.assign({}, baseProps, {showProcessing: true}));\n\n const overlay = node.querySelector('.pvtLoadingOverlay');\n expect(overlay).not.toBeNull();\n expect(overlay.parentElement.classList.contains('pvtOutput')).toBe(true);\n // The overlay is layered on top of the table: the table must survive.\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n // Only a spinner and a label: progress percentages were removed, the\n // overlay simply stays until the worker posts its result event.\n expect(overlay.querySelector('.pvtLoadingSpinner')).not.toBeNull();\n expect(overlay.querySelector('.pvtLoadingText').textContent).toBe(\n 'Processing'\n );\n expect(overlay.querySelector('.pvtLoadingBar')).toBeNull();\n expect(overlay.querySelector('.pvtLoadingPercent')).toBeNull();\n\n // Turning the flag back off removes the overlay.\n rerenderPivot(root, baseProps);\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n });\n\n it('renders a spinner without any percentage markup', function() {\n const node = document.createElement('div');\n act(() => {\n createRoot(node).render(React.createElement(PivotLoadingOverlay, null));\n });\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n expect(node.querySelector('.pvtLoadingSpinner')).not.toBeNull();\n expect(node.querySelector('.pvtLoadingBarWrap')).toBeNull();\n expect(node.querySelector('.pvtLoadingPercent')).toBeNull();\n expect(node.querySelector('.pvtLoadingText').textContent).toBe(\n 'Processing'\n );\n });\n\n it('hides the overlay only after the worker responds and the table renders', function() {\n const wait = ms => new Promise(resolve => setTimeout(resolve, ms));\n // React's act() returns a non-standard thenable whose .then() does not\n // return a promise, so it cannot be chained directly - wrap it first.\n const actAsync = callback =>\n new Promise((resolve, reject) => act(callback).then(resolve, reject));\n const workerInstances = [];\n class FakeWorker {\n constructor(url) {\n this.url = url;\n this.posted = [];\n this.onmessage = null;\n workerInstances.push(this);\n }\n\n postMessage(message) {\n this.posted.push(message);\n }\n\n terminate() {}\n }\n\n const previousWorker = global.Worker;\n global.Worker = FakeWorker;\n\n // workerThreshold: 2 makes this small dataset worker-eligible.\n const props = Object.assign({}, baseProps, {workerThreshold: 2});\n const {node} = mount(props);\n\n const restore = () => {\n global.Worker = previousWorker;\n };\n\n // The overlay is up while the computation is pending.\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n\n // Wait out the dispatch debounce so the request reaches the worker.\n return actAsync(() => wait(WORKER_DEBOUNCE_MS + 50))\n .then(() => {\n const worker = workerInstances[0];\n expect(worker).toBeDefined();\n const computeMsg = worker.posted.find(m => m.type === 'pivot:compute');\n expect(computeMsg).toBeDefined();\n // The worker has not answered yet: the overlay must stay up.\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n\n // Worker finishes and posts its result event.\n const result = computePivotState(\n materializeData(records, {}),\n createWorkerConfig(props)\n );\n return actAsync(() => {\n worker.onmessage({\n data: {type: 'pivot:result', id: computeMsg.id, result},\n });\n return wait(0);\n });\n })\n .then(() => {\n // The overlay is gone only now - and the freshly computed table is\n // already on screen in the same state where the overlay disappeared.\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n restore();\n })\n .catch(error => {\n restore();\n throw error;\n });\n });\n});\n"]}
@@ -0,0 +1,314 @@
1
+ 'use strict';
2
+
3
+ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4
+
5
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
6
+
7
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8
+
9
+ var _react = require('react');
10
+
11
+ var _react2 = _interopRequireDefault(_react);
12
+
13
+ var _client = require('react-dom/client');
14
+
15
+ var _PivotTableUI = require('../PivotTableUI');
16
+
17
+ var _PivotTableUI2 = _interopRequireDefault(_PivotTableUI);
18
+
19
+ var _pivotCompute = require('../pivotCompute');
20
+
21
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
+
23
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
24
+
25
+ function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
26
+
27
+ function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
28
+
29
+ /* eslint-disable no-magic-numbers */
30
+
31
+ // React 19 needs to be told that this environment supports `act`.
32
+ global.IS_REACT_ACT_ENVIRONMENT = true;
33
+
34
+ /*
35
+ * Regression tests for the "drag & drop freezes the browser" bug on large
36
+ * datasets.
37
+ *
38
+ * The worker keeps the *aggregation* off the main thread, but the rendered
39
+ * table itself cannot be: a table with 10k rows x 25 row attributes is ~270k
40
+ * cells. Every prop that changes identity on a parent re-render (`onColSort`,
41
+ * `onRowSort`, `colSorts`, `rowSorts`) defeated PivotTable's PureComponent
42
+ * check and made React re-render that whole table - on every drag event, every
43
+ * keystroke and even while a worker computation was still pending. These tests
44
+ * count renderer invocations to pin that behaviour down.
45
+ */
46
+ var records = [{ name: 'Nick', gender: 'male', colour: 'blue', trials: 103 }, { name: 'Jane', gender: 'female', colour: 'red', trials: 95 }, { name: 'John', gender: 'male', colour: 'blue', trials: 112 }, { name: 'Carol', gender: 'female', colour: 'yellow', trials: 102 }];
47
+
48
+ var renders = void 0;
49
+ var WorkerInstances = [];
50
+ var nextWorkerUrl = 1;
51
+
52
+ // Stands in for the real Table renderer: a PureComponent, exactly like the
53
+ // renderers built by `makeRenderer`, recording every render it is asked for.
54
+ // Rendering the real table is what costs seconds on a 10k-row pivot, so the
55
+ // number of calls here is the metric that matters.
56
+
57
+ var CountingRenderer = function (_React$PureComponent) {
58
+ _inherits(CountingRenderer, _React$PureComponent);
59
+
60
+ function CountingRenderer() {
61
+ _classCallCheck(this, CountingRenderer);
62
+
63
+ return _possibleConstructorReturn(this, (CountingRenderer.__proto__ || Object.getPrototypeOf(CountingRenderer)).apply(this, arguments));
64
+ }
65
+
66
+ _createClass(CountingRenderer, [{
67
+ key: 'render',
68
+ value: function render() {
69
+ renders.push({
70
+ rows: (this.props.rows || []).join('|'),
71
+ rowKeys: this.props.precomputedData ? this.props.precomputedData.rowKeys.length : null
72
+ });
73
+ return _react2.default.createElement('table', { className: 'pvtTable' });
74
+ }
75
+ }]);
76
+
77
+ return CountingRenderer;
78
+ }(_react2.default.PureComponent);
79
+
80
+ // Stable identities, mirroring what a consumer must do for the PureComponent
81
+ // bail-out to work (see the `renderers` constant in examples/App.jsx).
82
+
83
+
84
+ var stubRenderers = { Stub: CountingRenderer };
85
+
86
+ var baseProps = function baseProps(extra) {
87
+ return Object.assign({
88
+ data: records,
89
+ rows: ['name'],
90
+ cols: [],
91
+ aggregatorName: 'Count',
92
+ vals: [],
93
+ rendererName: 'Stub',
94
+ renderers: stubRenderers
95
+ }, extra);
96
+ };
97
+
98
+ // Emulates how examples/App.jsx drives PivotTableUI: the component is
99
+ // controlled, so `onChange` feeds the next state back in as props.
100
+ var harnessRef = { setState: null };
101
+
102
+ function Harness(props) {
103
+ var _React$useState = _react2.default.useState(props.initialState),
104
+ _React$useState2 = _slicedToArray(_React$useState, 2),
105
+ state = _React$useState2[0],
106
+ setState = _React$useState2[1];
107
+
108
+ harnessRef.setState = setState;
109
+ return _react2.default.createElement(_PivotTableUI2.default, _extends({}, state, {
110
+ renderers: stubRenderers,
111
+ rendererName: 'Stub',
112
+ workerThreshold: props.workerThreshold,
113
+ onChange: function onChange(next) {
114
+ return setState(next);
115
+ }
116
+ }));
117
+ }
118
+
119
+ var FakeWorker = function () {
120
+ function FakeWorker(url) {
121
+ _classCallCheck(this, FakeWorker);
122
+
123
+ this.url = url;
124
+ this.posted = [];
125
+ this.onmessage = null;
126
+ this.onerror = null;
127
+ WorkerInstances.push(this);
128
+ }
129
+
130
+ _createClass(FakeWorker, [{
131
+ key: 'postMessage',
132
+ value: function postMessage(message) {
133
+ this.posted.push(message);
134
+ }
135
+ }, {
136
+ key: 'terminate',
137
+ value: function terminate() {}
138
+ }]);
139
+
140
+ return FakeWorker;
141
+ }();
142
+
143
+ var wait = function wait(ms) {
144
+ return new Promise(function (resolve) {
145
+ return setTimeout(resolve, ms);
146
+ });
147
+ };
148
+ // React's act() returns a non-standard thenable whose .then() does not return
149
+ // a promise, so it cannot be chained directly - wrap it first.
150
+ var actAsync = function actAsync(callback) {
151
+ return new Promise(function (resolve, reject) {
152
+ return (0, _react.act)(callback).then(resolve, reject);
153
+ });
154
+ };
155
+
156
+ // The worker client keeps one shared worker per module, so before every test
157
+ // the client is pointed at a new URL: that discards the worker of the previous
158
+ // test and lets the next one create (and answer) its own fake worker.
159
+ var freshWorkerClient = function freshWorkerClient() {
160
+ var client = require('../PivotDataWorker');
161
+ client.configureWorkerUrl('pivot-worker.js?test=' + nextWorkerUrl++);
162
+ return client;
163
+ };
164
+
165
+ describe(' PivotTableUI re-render guards', function () {
166
+ var mounted = void 0;
167
+
168
+ beforeEach(function () {
169
+ renders = [];
170
+ WorkerInstances = [];
171
+ freshWorkerClient();
172
+ global.Worker = FakeWorker;
173
+ mounted = null;
174
+ });
175
+
176
+ afterEach(function () {
177
+ if (mounted) {
178
+ (0, _react.act)(function () {
179
+ mounted.root.unmount();
180
+ });
181
+ mounted.node.remove();
182
+ mounted = null;
183
+ }
184
+ global.Worker = undefined;
185
+ });
186
+
187
+ var mount = function mount(element) {
188
+ var node = document.createElement('div');
189
+ document.body.appendChild(node);
190
+ var root = (0, _client.createRoot)(node);
191
+ (0, _react.act)(function () {
192
+ root.render(element);
193
+ });
194
+ mounted = { node: node, root: root };
195
+ return mounted;
196
+ };
197
+
198
+ // Computes and posts the result the worker would have produced.
199
+ var answerCompute = function answerCompute(config, id) {
200
+ var worker = WorkerInstances[0];
201
+ expect(worker).toBeDefined();
202
+ var result = (0, _pivotCompute.computePivotState)((0, _pivotCompute.materializeData)(records, {}), (0, _pivotCompute.createWorkerConfig)(config));
203
+ (0, _react.act)(function () {
204
+ worker.onmessage({ data: { type: 'pivot:result', id: id, result: result } });
205
+ });
206
+ return result;
207
+ };
208
+
209
+ // Finds the request the component just dispatched and answers it.
210
+ var answerNextCompute = function answerNextCompute(config) {
211
+ return actAsync(function () {
212
+ return wait(150);
213
+ }).then(function () {
214
+ var computeMsg = WorkerInstances[0].posted.filter(function (m) {
215
+ return m.type === 'pivot:compute';
216
+ }).pop();
217
+ expect(computeMsg).toBeDefined();
218
+ var result = answerCompute(config, computeMsg.id);
219
+ return actAsync(function () {
220
+ return wait(0);
221
+ }).then(function () {
222
+ return result;
223
+ });
224
+ });
225
+ };
226
+
227
+ it('does not re-render the table while a worker computation is pending', function () {
228
+ var props = baseProps({ workerThreshold: 2 });
229
+
230
+ var _mount = mount(_react2.default.createElement(Harness, { initialState: props, workerThreshold: props.workerThreshold })),
231
+ node = _mount.node;
232
+
233
+ var rendersBefore = 0;
234
+
235
+ // Nothing to show yet: the first computation has no previous table.
236
+ expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();
237
+ expect(node.querySelector('.pvtTable')).toBeNull();
238
+
239
+ return answerNextCompute(props).then(function () {
240
+ expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();
241
+ rendersBefore = renders.length;
242
+ expect(rendersBefore).toBeGreaterThan(0);
243
+ expect(renders[rendersBefore - 1].rows).toBe('name');
244
+ expect(renders[rendersBefore - 1].rowKeys).toBe(4);
245
+
246
+ // Simulate a drop: the parent applies the new row axis, exactly like
247
+ // examples/App.jsx does from PivotTableUI's onChange.
248
+ (0, _react.act)(function () {
249
+ harnessRef.setState(function (prev) {
250
+ return Object.assign({}, prev, { rows: ['gender'] });
251
+ });
252
+ });
253
+
254
+ // The overlay is up over the previous table and, crucially, the table
255
+ // was NOT re-rendered with the new axis while the result is missing:
256
+ // neither the pending render itself nor the state updates that follow
257
+ // it may touch the rendered table.
258
+ expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();
259
+ expect(node.querySelector('.pvtTable')).not.toBeNull();
260
+ expect(renders.length).toBe(rendersBefore);
261
+
262
+ return actAsync(function () {
263
+ return wait(150);
264
+ });
265
+ }).then(function () {
266
+ expect(renders.length).toBe(rendersBefore);
267
+ var computeMsg = WorkerInstances[0].posted.filter(function (m) {
268
+ return m.type === 'pivot:compute';
269
+ }).pop();
270
+ answerCompute(Object.assign({}, props, { rows: ['gender'] }), computeMsg.id);
271
+ return actAsync(function () {
272
+ return wait(0);
273
+ });
274
+ }).then(function () {
275
+ // Exactly one render for the new result: the render that follows it
276
+ // (overlay removal) must bail out through the PureComponent check.
277
+ expect(renders.length).toBe(rendersBefore + 1);
278
+ expect(renders[rendersBefore].rows).toBe('gender');
279
+ expect(renders[rendersBefore].rowKeys).toBe(2);
280
+ expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();
281
+ expect(node.querySelector('.pvtTable')).not.toBeNull();
282
+ });
283
+ });
284
+
285
+ it('does not re-render the table when the parent re-renders unchanged props', function () {
286
+ var props = baseProps();
287
+
288
+ var _mount2 = mount(_react2.default.createElement(_PivotTableUI2.default, props)),
289
+ node = _mount2.node,
290
+ root = _mount2.root;
291
+
292
+ // Small datasets are not worker-eligible: the classic main-thread path
293
+ // renders the table synchronously on mount (no overlay, no pending state).
294
+
295
+
296
+ return actAsync(function () {
297
+ return wait(0);
298
+ }).then(function () {
299
+ var rendersBefore = renders.length;
300
+ expect(rendersBefore).toBeGreaterThan(0);
301
+
302
+ // A parent re-render with the very same props (an unrelated App state
303
+ // change) must not touch the table.
304
+ (0, _react.act)(function () {
305
+ root.render(_react2.default.createElement(_PivotTableUI2.default, props));
306
+ });
307
+
308
+ expect(renders.length).toBe(rendersBefore);
309
+ expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();
310
+ expect(node.querySelector('.pvtTable')).not.toBeNull();
311
+ });
312
+ });
313
+ });
314
+ //# sourceMappingURL=PivotTableUI-rerender-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/__tests__/PivotTableUI-rerender-test.js"],"names":["global","IS_REACT_ACT_ENVIRONMENT","records","name","gender","colour","trials","renders","WorkerInstances","nextWorkerUrl","CountingRenderer","push","rows","props","join","rowKeys","precomputedData","length","React","PureComponent","stubRenderers","Stub","baseProps","Object","assign","data","cols","aggregatorName","vals","rendererName","renderers","extra","harnessRef","setState","Harness","useState","initialState","state","workerThreshold","next","FakeWorker","url","posted","onmessage","onerror","message","wait","Promise","setTimeout","resolve","ms","actAsync","reject","callback","then","freshWorkerClient","client","require","configureWorkerUrl","describe","mounted","beforeEach","Worker","afterEach","root","unmount","node","remove","undefined","mount","document","createElement","body","appendChild","render","element","answerCompute","config","id","worker","expect","toBeDefined","result","type","answerNextCompute","computeMsg","filter","m","pop","it","rendersBefore","querySelector","not","toBeNull","toBeGreaterThan","toBe","prev"],"mappings":";;;;;;;;AAAA;;;;AACA;;AACA;;;;AACA;;;;;;;;;;AAKA;;AAEA;AACAA,OAAOC,wBAAP,GAAkC,IAAlC;;AAEA;;;;;;;;;;;;AAYA,IAAMC,UAAU,CACd,EAACC,MAAM,MAAP,EAAeC,QAAQ,MAAvB,EAA+BC,QAAQ,MAAvC,EAA+CC,QAAQ,GAAvD,EADc,EAEd,EAACH,MAAM,MAAP,EAAeC,QAAQ,QAAvB,EAAiCC,QAAQ,KAAzC,EAAgDC,QAAQ,EAAxD,EAFc,EAGd,EAACH,MAAM,MAAP,EAAeC,QAAQ,MAAvB,EAA+BC,QAAQ,MAAvC,EAA+CC,QAAQ,GAAvD,EAHc,EAId,EAACH,MAAM,OAAP,EAAgBC,QAAQ,QAAxB,EAAkCC,QAAQ,QAA1C,EAAoDC,QAAQ,GAA5D,EAJc,CAAhB;;AAOA,IAAIC,gBAAJ;AACA,IAAIC,kBAAkB,EAAtB;AACA,IAAIC,gBAAgB,CAApB;;AAEA;AACA;AACA;AACA;;IACMC,gB;;;;;;;;;;;6BACK;AACPH,cAAQI,IAAR,CAAa;AACXC,cAAM,CAAC,KAAKC,KAAL,CAAWD,IAAX,IAAmB,EAApB,EAAwBE,IAAxB,CAA6B,GAA7B,CADK;AAEXC,iBAAS,KAAKF,KAAL,CAAWG,eAAX,GACL,KAAKH,KAAL,CAAWG,eAAX,CAA2BD,OAA3B,CAAmCE,MAD9B,GAEL;AAJO,OAAb;AAMA,aAAO,yCAAO,WAAU,UAAjB,GAAP;AACD;;;;EAT4BC,gBAAMC,a;;AAYrC;AACA;;;AACA,IAAMC,gBAAgB,EAACC,MAAMX,gBAAP,EAAtB;;AAEA,IAAMY,YAAY,SAAZA,SAAY;AAAA,SAChBC,OAAOC,MAAP,CACE;AACEC,UAAMvB,OADR;AAEEU,UAAM,CAAC,MAAD,CAFR;AAGEc,UAAM,EAHR;AAIEC,oBAAgB,OAJlB;AAKEC,UAAM,EALR;AAMEC,kBAAc,MANhB;AAOEC,eAAWV;AAPb,GADF,EAUEW,KAVF,CADgB;AAAA,CAAlB;;AAcA;AACA;AACA,IAAMC,aAAa,EAACC,UAAU,IAAX,EAAnB;;AAEA,SAASC,OAAT,CAAiBrB,KAAjB,EAAwB;AAAA,wBACIK,gBAAMiB,QAAN,CAAetB,MAAMuB,YAArB,CADJ;AAAA;AAAA,MACfC,KADe;AAAA,MACRJ,QADQ;;AAEtBD,aAAWC,QAAX,GAAsBA,QAAtB;AACA,SACE,8BAAC,sBAAD,eACMI,KADN;AAEE,eAAWjB,aAFb;AAGE,kBAAa,MAHf;AAIE,qBAAiBP,MAAMyB,eAJzB;AAKE,cAAU;AAAA,aAAQL,SAASM,IAAT,CAAR;AAAA;AALZ,KADF;AASD;;IAEKC,U;AACJ,sBAAYC,GAAZ,EAAiB;AAAA;;AACf,SAAKA,GAAL,GAAWA,GAAX;AACA,SAAKC,MAAL,GAAc,EAAd;AACA,SAAKC,SAAL,GAAiB,IAAjB;AACA,SAAKC,OAAL,GAAe,IAAf;AACApC,oBAAgBG,IAAhB,CAAqB,IAArB;AACD;;;;gCAEWkC,O,EAAS;AACnB,WAAKH,MAAL,CAAY/B,IAAZ,CAAiBkC,OAAjB;AACD;;;gCAEW,CAAE;;;;;;AAGhB,IAAMC,OAAO,SAAPA,IAAO;AAAA,SAAM,IAAIC,OAAJ,CAAY;AAAA,WAAWC,WAAWC,OAAX,EAAoBC,EAApB,CAAX;AAAA,GAAZ,CAAN;AAAA,CAAb;AACA;AACA;AACA,IAAMC,WAAW,SAAXA,QAAW;AAAA,SACf,IAAIJ,OAAJ,CAAY,UAACE,OAAD,EAAUG,MAAV;AAAA,WAAqB,gBAAIC,QAAJ,EAAcC,IAAd,CAAmBL,OAAnB,EAA4BG,MAA5B,CAArB;AAAA,GAAZ,CADe;AAAA,CAAjB;;AAGA;AACA;AACA;AACA,IAAMG,oBAAoB,SAApBA,iBAAoB,GAAM;AAC9B,MAAMC,SAASC,QAAQ,oBAAR,CAAf;AACAD,SAAOE,kBAAP,2BAAkDjD,eAAlD;AACA,SAAO+C,MAAP;AACD,CAJD;;AAMAG,SAAS,iCAAT,EAA4C,YAAW;AACrD,MAAIC,gBAAJ;;AAEAC,aAAW,YAAW;AACpBtD,cAAU,EAAV;AACAC,sBAAkB,EAAlB;AACA+C;AACAvD,WAAO8D,MAAP,GAAgBtB,UAAhB;AACAoB,cAAU,IAAV;AACD,GAND;;AAQAG,YAAU,YAAW;AACnB,QAAIH,OAAJ,EAAa;AACX,sBAAI,YAAM;AACRA,gBAAQI,IAAR,CAAaC,OAAb;AACD,OAFD;AAGAL,cAAQM,IAAR,CAAaC,MAAb;AACAP,gBAAU,IAAV;AACD;AACD5D,WAAO8D,MAAP,GAAgBM,SAAhB;AACD,GATD;;AAWA,MAAMC,QAAQ,SAARA,KAAQ,UAAW;AACvB,QAAMH,OAAOI,SAASC,aAAT,CAAuB,KAAvB,CAAb;AACAD,aAASE,IAAT,CAAcC,WAAd,CAA0BP,IAA1B;AACA,QAAMF,OAAO,wBAAWE,IAAX,CAAb;AACA,oBAAI,YAAM;AACRF,WAAKU,MAAL,CAAYC,OAAZ;AACD,KAFD;AAGAf,cAAU,EAACM,UAAD,EAAOF,UAAP,EAAV;AACA,WAAOJ,OAAP;AACD,GATD;;AAWA;AACA,MAAMgB,gBAAgB,SAAhBA,aAAgB,CAACC,MAAD,EAASC,EAAT,EAAgB;AACpC,QAAMC,SAASvE,gBAAgB,CAAhB,CAAf;AACAwE,WAAOD,MAAP,EAAeE,WAAf;AACA,QAAMC,SAAS,qCACb,mCAAgBhF,OAAhB,EAAyB,EAAzB,CADa,EAEb,sCAAmB2E,MAAnB,CAFa,CAAf;AAIA,oBAAI,YAAM;AACRE,aAAOpC,SAAP,CAAiB,EAAClB,MAAM,EAAC0D,MAAM,cAAP,EAAuBL,MAAvB,EAA2BI,cAA3B,EAAP,EAAjB;AACD,KAFD;AAGA,WAAOA,MAAP;AACD,GAXD;;AAaA;AACA,MAAME,oBAAoB,SAApBA,iBAAoB;AAAA,WACxBjC,SAAS;AAAA,aAAML,KAAK,GAAL,CAAN;AAAA,KAAT,EAA0BQ,IAA1B,CAA+B,YAAM;AACnC,UAAM+B,aAAa7E,gBAAgB,CAAhB,EAAmBkC,MAAnB,CAChB4C,MADgB,CACT;AAAA,eAAKC,EAAEJ,IAAF,KAAW,eAAhB;AAAA,OADS,EAEhBK,GAFgB,EAAnB;AAGAR,aAAOK,UAAP,EAAmBJ,WAAnB;AACA,UAAMC,SAASN,cAAcC,MAAd,EAAsBQ,WAAWP,EAAjC,CAAf;AACA,aAAO3B,SAAS;AAAA,eAAML,KAAK,CAAL,CAAN;AAAA,OAAT,EAAwBQ,IAAxB,CAA6B;AAAA,eAAM4B,MAAN;AAAA,OAA7B,CAAP;AACD,KAPD,CADwB;AAAA,GAA1B;;AAUAO,KAAG,oEAAH,EAAyE,YAAW;AAClF,QAAM5E,QAAQS,UAAU,EAACgB,iBAAiB,CAAlB,EAAV,CAAd;;AADkF,iBAEnE+B,MACb,8BAAC,OAAD,IAAS,cAAcxD,KAAvB,EAA8B,iBAAiBA,MAAMyB,eAArD,GADa,CAFmE;AAAA,QAE3E4B,IAF2E,UAE3EA,IAF2E;;AAKlF,QAAIwB,gBAAgB,CAApB;;AAEA;AACAV,WAAOd,KAAKyB,aAAL,CAAmB,oBAAnB,CAAP,EAAiDC,GAAjD,CAAqDC,QAArD;AACAb,WAAOd,KAAKyB,aAAL,CAAmB,WAAnB,CAAP,EAAwCE,QAAxC;;AAEA,WAAOT,kBAAkBvE,KAAlB,EACJyC,IADI,CACC,YAAM;AACV0B,aAAOd,KAAKyB,aAAL,CAAmB,oBAAnB,CAAP,EAAiDE,QAAjD;AACAH,sBAAgBnF,QAAQU,MAAxB;AACA+D,aAAOU,aAAP,EAAsBI,eAAtB,CAAsC,CAAtC;AACAd,aAAOzE,QAAQmF,gBAAgB,CAAxB,EAA2B9E,IAAlC,EAAwCmF,IAAxC,CAA6C,MAA7C;AACAf,aAAOzE,QAAQmF,gBAAgB,CAAxB,EAA2B3E,OAAlC,EAA2CgF,IAA3C,CAAgD,CAAhD;;AAEA;AACA;AACA,sBAAI,YAAM;AACR/D,mBAAWC,QAAX,CAAoB;AAAA,iBAClBV,OAAOC,MAAP,CAAc,EAAd,EAAkBwE,IAAlB,EAAwB,EAACpF,MAAM,CAAC,QAAD,CAAP,EAAxB,CADkB;AAAA,SAApB;AAGD,OAJD;;AAMA;AACA;AACA;AACA;AACAoE,aAAOd,KAAKyB,aAAL,CAAmB,oBAAnB,CAAP,EAAiDC,GAAjD,CAAqDC,QAArD;AACAb,aAAOd,KAAKyB,aAAL,CAAmB,WAAnB,CAAP,EAAwCC,GAAxC,CAA4CC,QAA5C;AACAb,aAAOzE,QAAQU,MAAf,EAAuB8E,IAAvB,CAA4BL,aAA5B;;AAEA,aAAOvC,SAAS;AAAA,eAAML,KAAK,GAAL,CAAN;AAAA,OAAT,CAAP;AACD,KAzBI,EA0BJQ,IA1BI,CA0BC,YAAM;AACV0B,aAAOzE,QAAQU,MAAf,EAAuB8E,IAAvB,CAA4BL,aAA5B;AACA,UAAML,aAAa7E,gBAAgB,CAAhB,EAAmBkC,MAAnB,CAChB4C,MADgB,CACT;AAAA,eAAKC,EAAEJ,IAAF,KAAW,eAAhB;AAAA,OADS,EAEhBK,GAFgB,EAAnB;AAGAZ,oBACErD,OAAOC,MAAP,CAAc,EAAd,EAAkBX,KAAlB,EAAyB,EAACD,MAAM,CAAC,QAAD,CAAP,EAAzB,CADF,EAEEyE,WAAWP,EAFb;AAIA,aAAO3B,SAAS;AAAA,eAAML,KAAK,CAAL,CAAN;AAAA,OAAT,CAAP;AACD,KApCI,EAqCJQ,IArCI,CAqCC,YAAM;AACV;AACA;AACA0B,aAAOzE,QAAQU,MAAf,EAAuB8E,IAAvB,CAA4BL,gBAAgB,CAA5C;AACAV,aAAOzE,QAAQmF,aAAR,EAAuB9E,IAA9B,EAAoCmF,IAApC,CAAyC,QAAzC;AACAf,aAAOzE,QAAQmF,aAAR,EAAuB3E,OAA9B,EAAuCgF,IAAvC,CAA4C,CAA5C;AACAf,aAAOd,KAAKyB,aAAL,CAAmB,oBAAnB,CAAP,EAAiDE,QAAjD;AACAb,aAAOd,KAAKyB,aAAL,CAAmB,WAAnB,CAAP,EAAwCC,GAAxC,CAA4CC,QAA5C;AACD,KA7CI,CAAP;AA8CD,GAzDD;;AA2DAJ,KAAG,yEAAH,EAA8E,YAAW;AACvF,QAAM5E,QAAQS,WAAd;;AADuF,kBAElE+C,MAAM,8BAAC,sBAAD,EAAkBxD,KAAlB,CAAN,CAFkE;AAAA,QAEhFqD,IAFgF,WAEhFA,IAFgF;AAAA,QAE1EF,IAF0E,WAE1EA,IAF0E;;AAIvF;AACA;;;AACA,WAAOb,SAAS;AAAA,aAAML,KAAK,CAAL,CAAN;AAAA,KAAT,EAAwBQ,IAAxB,CAA6B,YAAM;AACxC,UAAMoC,gBAAgBnF,QAAQU,MAA9B;AACA+D,aAAOU,aAAP,EAAsBI,eAAtB,CAAsC,CAAtC;;AAEA;AACA;AACA,sBAAI,YAAM;AACR9B,aAAKU,MAAL,CAAY,8BAAC,sBAAD,EAAkB7D,KAAlB,CAAZ;AACD,OAFD;;AAIAmE,aAAOzE,QAAQU,MAAf,EAAuB8E,IAAvB,CAA4BL,aAA5B;AACAV,aAAOd,KAAKyB,aAAL,CAAmB,oBAAnB,CAAP,EAAiDE,QAAjD;AACAb,aAAOd,KAAKyB,aAAL,CAAmB,WAAnB,CAAP,EAAwCC,GAAxC,CAA4CC,QAA5C;AACD,KAbM,CAAP;AAcD,GApBD;AAqBD,CA1ID","file":"PivotTableUI-rerender-test.js","sourcesContent":["import React, {act} from 'react';\nimport {createRoot} from 'react-dom/client';\nimport PivotTableUI from '../PivotTableUI';\nimport {\n computePivotState,\n createWorkerConfig,\n materializeData,\n} from '../pivotCompute';\n/* eslint-disable no-magic-numbers */\n\n// React 19 needs to be told that this environment supports `act`.\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\n/*\n * Regression tests for the \"drag & drop freezes the browser\" bug on large\n * datasets.\n *\n * The worker keeps the *aggregation* off the main thread, but the rendered\n * table itself cannot be: a table with 10k rows x 25 row attributes is ~270k\n * cells. Every prop that changes identity on a parent re-render (`onColSort`,\n * `onRowSort`, `colSorts`, `rowSorts`) defeated PivotTable's PureComponent\n * check and made React re-render that whole table - on every drag event, every\n * keystroke and even while a worker computation was still pending. These tests\n * count renderer invocations to pin that behaviour down.\n */\nconst records = [\n {name: 'Nick', gender: 'male', colour: 'blue', trials: 103},\n {name: 'Jane', gender: 'female', colour: 'red', trials: 95},\n {name: 'John', gender: 'male', colour: 'blue', trials: 112},\n {name: 'Carol', gender: 'female', colour: 'yellow', trials: 102},\n];\n\nlet renders;\nlet WorkerInstances = [];\nlet nextWorkerUrl = 1;\n\n// Stands in for the real Table renderer: a PureComponent, exactly like the\n// renderers built by `makeRenderer`, recording every render it is asked for.\n// Rendering the real table is what costs seconds on a 10k-row pivot, so the\n// number of calls here is the metric that matters.\nclass CountingRenderer extends React.PureComponent {\n render() {\n renders.push({\n rows: (this.props.rows || []).join('|'),\n rowKeys: this.props.precomputedData\n ? this.props.precomputedData.rowKeys.length\n : null,\n });\n return <table className=\"pvtTable\" />;\n }\n}\n\n// Stable identities, mirroring what a consumer must do for the PureComponent\n// bail-out to work (see the `renderers` constant in examples/App.jsx).\nconst stubRenderers = {Stub: CountingRenderer};\n\nconst baseProps = extra =>\n Object.assign(\n {\n data: records,\n rows: ['name'],\n cols: [],\n aggregatorName: 'Count',\n vals: [],\n rendererName: 'Stub',\n renderers: stubRenderers,\n },\n extra\n );\n\n// Emulates how examples/App.jsx drives PivotTableUI: the component is\n// controlled, so `onChange` feeds the next state back in as props.\nconst harnessRef = {setState: null};\n\nfunction Harness(props) {\n const [state, setState] = React.useState(props.initialState);\n harnessRef.setState = setState;\n return (\n <PivotTableUI\n {...state}\n renderers={stubRenderers}\n rendererName=\"Stub\"\n workerThreshold={props.workerThreshold}\n onChange={next => setState(next)}\n />\n );\n}\n\nclass FakeWorker {\n constructor(url) {\n this.url = url;\n this.posted = [];\n this.onmessage = null;\n this.onerror = null;\n WorkerInstances.push(this);\n }\n\n postMessage(message) {\n this.posted.push(message);\n }\n\n terminate() {}\n}\n\nconst wait = ms => new Promise(resolve => setTimeout(resolve, ms));\n// React's act() returns a non-standard thenable whose .then() does not return\n// a promise, so it cannot be chained directly - wrap it first.\nconst actAsync = callback =>\n new Promise((resolve, reject) => act(callback).then(resolve, reject));\n\n// The worker client keeps one shared worker per module, so before every test\n// the client is pointed at a new URL: that discards the worker of the previous\n// test and lets the next one create (and answer) its own fake worker.\nconst freshWorkerClient = () => {\n const client = require('../PivotDataWorker');\n client.configureWorkerUrl(`pivot-worker.js?test=${nextWorkerUrl++}`);\n return client;\n};\n\ndescribe(' PivotTableUI re-render guards', function() {\n let mounted;\n\n beforeEach(function() {\n renders = [];\n WorkerInstances = [];\n freshWorkerClient();\n global.Worker = FakeWorker;\n mounted = null;\n });\n\n afterEach(function() {\n if (mounted) {\n act(() => {\n mounted.root.unmount();\n });\n mounted.node.remove();\n mounted = null;\n }\n global.Worker = undefined;\n });\n\n const mount = element => {\n const node = document.createElement('div');\n document.body.appendChild(node);\n const root = createRoot(node);\n act(() => {\n root.render(element);\n });\n mounted = {node, root};\n return mounted;\n };\n\n // Computes and posts the result the worker would have produced.\n const answerCompute = (config, id) => {\n const worker = WorkerInstances[0];\n expect(worker).toBeDefined();\n const result = computePivotState(\n materializeData(records, {}),\n createWorkerConfig(config)\n );\n act(() => {\n worker.onmessage({data: {type: 'pivot:result', id, result}});\n });\n return result;\n };\n\n // Finds the request the component just dispatched and answers it.\n const answerNextCompute = config =>\n actAsync(() => wait(150)).then(() => {\n const computeMsg = WorkerInstances[0].posted\n .filter(m => m.type === 'pivot:compute')\n .pop();\n expect(computeMsg).toBeDefined();\n const result = answerCompute(config, computeMsg.id);\n return actAsync(() => wait(0)).then(() => result);\n });\n\n it('does not re-render the table while a worker computation is pending', function() {\n const props = baseProps({workerThreshold: 2});\n const {node} = mount(\n <Harness initialState={props} workerThreshold={props.workerThreshold} />\n );\n let rendersBefore = 0;\n\n // Nothing to show yet: the first computation has no previous table.\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n expect(node.querySelector('.pvtTable')).toBeNull();\n\n return answerNextCompute(props)\n .then(() => {\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n rendersBefore = renders.length;\n expect(rendersBefore).toBeGreaterThan(0);\n expect(renders[rendersBefore - 1].rows).toBe('name');\n expect(renders[rendersBefore - 1].rowKeys).toBe(4);\n\n // Simulate a drop: the parent applies the new row axis, exactly like\n // examples/App.jsx does from PivotTableUI's onChange.\n act(() => {\n harnessRef.setState(prev =>\n Object.assign({}, prev, {rows: ['gender']})\n );\n });\n\n // The overlay is up over the previous table and, crucially, the table\n // was NOT re-rendered with the new axis while the result is missing:\n // neither the pending render itself nor the state updates that follow\n // it may touch the rendered table.\n expect(node.querySelector('.pvtLoadingOverlay')).not.toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n expect(renders.length).toBe(rendersBefore);\n\n return actAsync(() => wait(150));\n })\n .then(() => {\n expect(renders.length).toBe(rendersBefore);\n const computeMsg = WorkerInstances[0].posted\n .filter(m => m.type === 'pivot:compute')\n .pop();\n answerCompute(\n Object.assign({}, props, {rows: ['gender']}),\n computeMsg.id\n );\n return actAsync(() => wait(0));\n })\n .then(() => {\n // Exactly one render for the new result: the render that follows it\n // (overlay removal) must bail out through the PureComponent check.\n expect(renders.length).toBe(rendersBefore + 1);\n expect(renders[rendersBefore].rows).toBe('gender');\n expect(renders[rendersBefore].rowKeys).toBe(2);\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n });\n });\n\n it('does not re-render the table when the parent re-renders unchanged props', function() {\n const props = baseProps();\n const {node, root} = mount(<PivotTableUI {...props} />);\n\n // Small datasets are not worker-eligible: the classic main-thread path\n // renders the table synchronously on mount (no overlay, no pending state).\n return actAsync(() => wait(0)).then(() => {\n const rendersBefore = renders.length;\n expect(rendersBefore).toBeGreaterThan(0);\n\n // A parent re-render with the very same props (an unrelated App state\n // change) must not touch the table.\n act(() => {\n root.render(<PivotTableUI {...props} />);\n });\n\n expect(renders.length).toBe(rendersBefore);\n expect(node.querySelector('.pvtLoadingOverlay')).toBeNull();\n expect(node.querySelector('.pvtTable')).not.toBeNull();\n });\n });\n});\n"]}