Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/wrapper_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ jobs:
run: pnpx puppeteer browsers install chrome@130.0.6723.69

- name: Test ${{ matrix.framework }}
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpx nx test devextreme-${{ matrix.framework }}

- name: Pack ${{ matrix.framework }}
Expand Down
26 changes: 24 additions & 2 deletions packages/devextreme-angular/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,20 @@ module.exports = function (config) {

autoWatch: true,

browsers: ['ChromeHeadless'],
browsers: ['ChromeHeadlessWithGC'],

customLaunchers: {
ChromeHeadlessWithGC: {
base: 'ChromeHeadless',
flags: [
'--enable-features=MeasureMemory',
'--js-flags=--expose-gc',
'--no-sandbox',
'--disable-gpu',
'--enable-precise-memory-info',
],
},
},

reporters: [
'progress',
Expand All @@ -36,13 +49,22 @@ module.exports = function (config) {
junitReporter: {
outputFile: 'test-results.xml',
},

beforeMiddleware: ['customHeaders'],
// Karma plugins loaded
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-junit-reporter'),
require('karma-webpack'),
{
'middleware:customHeaders': ['factory', function () {
return function (req, res, next) {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
next();
};
}],
},
],

webpack: webpackConfig,
Expand Down
51 changes: 51 additions & 0 deletions packages/devextreme-angular/tests/src/ui/data-grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,54 @@ describe('Nested DxDataGrid', () => {
}, 1000);
});
});

describe('DxDataGrid slow tests', () => {
const originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;

beforeAll(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;

TestBed.configureTestingModule(
{
declarations: [TestContainerComponent],
imports: [DxDataGridModule],
},
);
});
afterAll(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});

it('should not memory leak after click if dx-data-grid is on page (T1307313)', async () => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: '<dx-data-grid [dataSource]="[]"></dx-data-grid>',
},
});

const fixture = TestBed.createComponent(TestContainerComponent);

fixture.detectChanges();

for (let i = 0; i < 100; i++) {
document.body.click();
fixture.detectChanges();
}

globalThis.gc();

const memoryBefore = await (performance as any).measureUserAgentSpecificMemory();

for (let i = 0; i < 100; i++) {
document.body.click();
fixture.detectChanges();
}

globalThis.gc();

const memoryAfter = await (performance as any).measureUserAgentSpecificMemory();
const memoryDiff = Math.round((memoryAfter.bytes - memoryBefore.bytes) / 1024);

expect(memoryDiff).toBeLessThan(30);
});
});
15 changes: 13 additions & 2 deletions packages/devextreme/js/__internal/events/m_click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const misc = { requestAnimationFrame, cancelAnimationFrame };

let prevented: boolean | null = null;
let lastFiredEvent = null;
const subscriptions = new Map();

const onNodeRemove = () => {
lastFiredEvent = null;
Expand All @@ -32,9 +33,19 @@ const clickHandler = function (e) {
originalEvent.DXCLICK_FIRED = true;
}

unsubscribeNodesDisposing(lastFiredEvent, onNodeRemove);
if (subscriptions.has(lastFiredEvent)) {
const { nodes, callback } = subscriptions.get(lastFiredEvent);

unsubscribeNodesDisposing(lastFiredEvent, callback, nodes);

subscriptions.delete(lastFiredEvent);
}

lastFiredEvent = originalEvent;
subscribeNodesDisposing(lastFiredEvent, onNodeRemove);

const subscriptionData = subscribeNodesDisposing(lastFiredEvent, onNodeRemove);

subscriptions.set(lastFiredEvent, subscriptionData);

fireEvent({
type: CLICK_EVENT_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,28 @@ function nodesByEvent(event) {
event.delegateTarget,
event.relatedTarget,
event.currentTarget,
].filter((node) => !!node);
].reduce((res, node) => {
if (!!node && !res.includes(node)) {
res.push(node);
}

return res;
}, []);
}

export const subscribeNodesDisposing = (event, callback) => {
eventsEngine.one(nodesByEvent(event), removeEvent, callback);
const nodes = nodesByEvent(event);
const onceCallback = function (...args) {
eventsEngine.off(nodes, removeEvent, onceCallback);

return callback(...args);
};

eventsEngine.on(nodes, removeEvent, onceCallback);

return { onceCallback, nodes };
};

export const unsubscribeNodesDisposing = (event, callback) => {
eventsEngine.off(nodesByEvent(event), removeEvent, callback);
export const unsubscribeNodesDisposing = (event, callback, nodes) => {
eventsEngine.off(nodes || nodesByEvent(event), removeEvent, callback);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import eventsEngine from 'common/core/events/core/events_engine';
import { removeEvent } from 'common/core/events/remove';
import {
subscribeNodesDisposing,
unsubscribeNodesDisposing,
} from '__internal/events/utils/m_event_nodes_disposing';

QUnit.testStart(function() {
const markup = '<button id="test-element">Test</button>';
const fixture = document.getElementById('qunit-fixture');
if(fixture) {
fixture.innerHTML = markup;
}
});

QUnit.module('event nodes disposing');

QUnit.test('should clean elementDataMap when using subscribeNodesDisposing and unsubscribeNodesDisposing for click', function(assert) {
const testElement = document.getElementById('test-element');

const clickEvent = eventsEngine.Event('click', {
target: testElement,
currentTarget: document,
delegateTarget: document
});

const subscriptionData = subscribeNodesDisposing(clickEvent, function() {});

const afterSubscribeElementData = eventsEngine.elementDataMap.get(document);
const afterSubscribeHandleObjectsCount = afterSubscribeElementData && afterSubscribeElementData[removeEvent]
? afterSubscribeElementData[removeEvent].handleObjects.length
: 0;

unsubscribeNodesDisposing(clickEvent, subscriptionData.callback, subscriptionData.nodes);

const finalElementData = eventsEngine.elementDataMap.get(document);
const afterUnsubscribeHandleObjectsCount = finalElementData && finalElementData[removeEvent]
? finalElementData[removeEvent].handleObjects.length
: 0;

assert.ok(
afterSubscribeHandleObjectsCount <= 1,
`HandleObjects should be added for "${removeEvent}" event after subscribe. HandleObjects count: ${afterSubscribeHandleObjectsCount};`
);

assert.equal(
afterUnsubscribeHandleObjectsCount,
0,
`HandleObjects should be removed for "${removeEvent}" event after unsubscribe. HandleObjects count: ${afterUnsubscribeHandleObjectsCount};`
);
});
Loading