diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4bdf1b7..ed0e8b8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,10 +25,17 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- - name: npm install, build, and test
+ - name: npm install and build
run: |
npm install
npm run build --if-present
- npm test
+ env:
+ CI: true
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps chromium
+
+ - name: Run tests
+ run: npm test
env:
CI: true
diff --git a/.gitignore b/.gitignore
index 39809be..39ee20f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@ npm-debug.*
.idea
coverage
lib
+test-results/
+playwright-report/
+package-lock.json
diff --git a/__tests__/index.spec.js b/__tests__/index.spec.js
deleted file mode 100644
index 2f10a4f..0000000
--- a/__tests__/index.spec.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { clear, bind, ver } from '../src';
-import pkg from '../package.json';
-import { injectStyle } from './utils';
-
-
-describe('size-sensor', () => {
- test('export', () => {
- expect(clear).toBeInstanceOf(Function);
- expect(bind).toBeInstanceOf(Function);
-
- expect(ver).toBe(pkg.version);
- });
-
- test('demo', () => {
- // 创建 div
- const div = document.createElement('div');
- document.body.appendChild(div);
- // 设置样式
- injectStyle();
-
- // div 结构
- div.innerHTML = `
-
- `;
-
- // 开始 bind
- const indicator = document.getElementById('size-indicator');
-
- const cb = ele => {
- const size = getComputedStyle(ele);
-
- indicator.innerHTML = size.width + ' x ' + size.height;
- indicator.style.color = 'red';
-
- setTimeout(() => {
- indicator.style.color = 'black';
- }, 500);
- };
-
- // bind an ele, when size changed, do cb function
- bind(document.getElementById('wrapper'), cb);
- });
-});
diff --git a/__tests__/issue-17.spec.js b/__tests__/issue-17.spec.js
deleted file mode 100644
index 5086be7..0000000
--- a/__tests__/issue-17.spec.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { clear, bind } from '../src';
-import { Sensors } from '../src/sensorPool';
-
-describe('#17', () => {
- // 创建 div
- const div = document.createElement('div');
- document.body.appendChild(div);
- div.innerHTML = ``;
-
- const wrapper = document.getElementById('wrapper');
-
- test('memory leak', () => {
- const unbind1 = bind(wrapper, () => {});
- const unbind2 = bind(wrapper, () => {});
-
- const id = wrapper.getAttribute('size-sensor-id');
-
- expect(Object.keys(Sensors).length).toBe(1);
- expect(Sensors[id]).not.toBeUndefined();
-
- unbind1();
- expect(Object.keys(Sensors).length).toBe(1);
- expect(Sensors[id]).not.toBeNull();
-
- unbind2();
- expect(Object.keys(Sensors).length).toBe(0);
- expect(Sensors[id]).toBeUndefined();
- });
-});
diff --git a/__tests__/unbind.spec.js b/__tests__/unbind.spec.js
deleted file mode 100644
index a704887..0000000
--- a/__tests__/unbind.spec.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { clear, bind } from '../src';
-
-describe('unbind', () => {
- // 创建 div
- const div = document.createElement('div');
- document.body.appendChild(div);
- div.innerHTML = ``;
-
- const wrapper = document.getElementById('wrapper');
-
- test('clear', () => {
- bind(wrapper, () => {});
- expect(wrapper.getAttribute('size-sensor-id')).not.toBeNull();
-
- clear(wrapper);
- expect(wrapper.getAttribute('size-sensor-id')).toBeNull();
- });
-
- test('unbind', () => {
- const unbind = bind(wrapper, () => {});
- expect(wrapper.getAttribute('size-sensor-id')).not.toBeNull();
-
- unbind();
- expect(wrapper.getAttribute('size-sensor-id')).toBeNull();
- });
-});
diff --git a/__tests__/utils.js b/__tests__/utils.js
deleted file mode 100644
index c320b31..0000000
--- a/__tests__/utils.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const CSS = `
-#wrapper {
- position: relative;
- height: 400px;
- background-color: #8bcdaf
-}
-
-
-#size-indicator {
- position: absolute;
- right: 4px;
- bottom: 4px;
- font-size: 10px;
- color: black;
-}
-`;
-
-export const injectStyle = (styleText = CSS) => {
- const style = document.createElement('style');
- style.type = 'text/css';
-
- if (style.styleSheet) {
- style.styleSheet.cssText = styleText;
- } else {
- style.appendChild(document.createTextNode(styleText));
- }
- document.head.appendChild(style);
-};
diff --git a/dist/size-sensor.min.js b/dist/size-sensor.min.js
index 46bb48b..7ab9f1d 100644
--- a/dist/size-sensor.min.js
+++ b/dist/size-sensor.min.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).sizeSensor={})}(this,function(e){"use strict";function u(o){var r=1{"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).sizeSensor={})})(this,function(e){function u(o){var r=1 {
+ test('export', async ({ page }) => {
+ // Load the size-sensor library
+ await page.setContent('');
+ await page.addScriptTag({ content: sizeSensorBundle });
+
+ // Test exports
+ const hasExports = await page.evaluate(() => {
+ const { bind, clear, ver } = window.sizeSensor;
+ return {
+ bindIsFunction: typeof bind === 'function',
+ clearIsFunction: typeof clear === 'function',
+ verExists: typeof ver === 'string' && ver.length > 0
+ };
+ });
+
+ expect(hasExports.bindIsFunction).toBe(true);
+ expect(hasExports.clearIsFunction).toBe(true);
+ expect(hasExports.verExists).toBe(true);
+ });
+
+ test('demo', async ({ page }) => {
+ // Create a page with the demo setup
+ await page.setContent(`
+
+
+
+
+
+
+
+
+ `);
+
+ // Load the size-sensor library
+ await page.addScriptTag({ content: sizeSensorBundle });
+
+ // Bind the sensor and verify it works
+ await page.evaluate(() => {
+ const { bind } = window.sizeSensor;
+ const indicator = document.getElementById('size-indicator');
+ const wrapper = document.getElementById('wrapper');
+
+ const cb = ele => {
+ const size = getComputedStyle(ele);
+ indicator.innerHTML = size.width + ' x ' + size.height;
+ indicator.style.color = 'red';
+
+ setTimeout(() => {
+ indicator.style.color = 'black';
+ }, 500);
+ };
+
+ // bind an element, when size changed, do cb function
+ bind(wrapper, cb);
+ });
+
+ // Verify the binding was successful
+ const hasSensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id') !== null;
+ });
+
+ expect(hasSensorId).toBe(true);
+ });
+});
diff --git a/tests/issue-17.spec.mjs b/tests/issue-17.spec.mjs
new file mode 100644
index 0000000..ab2e1eb
--- /dev/null
+++ b/tests/issue-17.spec.mjs
@@ -0,0 +1,75 @@
+import { test, expect } from '@playwright/test';
+import { readFileSync } from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Read the built UMD bundle
+const sizeSensorBundle = readFileSync(path.join(__dirname, '../dist/size-sensor.min.js'), 'utf8');
+
+test.describe('#17', () => {
+ test('memory leak', async ({ page }) => {
+ await page.setContent('');
+ await page.addScriptTag({ content: sizeSensorBundle });
+
+ // Create two bindings on the same element
+ await page.evaluate(() => {
+ const { bind } = window.sizeSensor;
+ const wrapper = document.getElementById('wrapper');
+
+ window.unbind1 = bind(wrapper, () => {});
+ window.unbind2 = bind(wrapper, () => {});
+ });
+
+ // Get the sensor ID
+ const sensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id');
+ });
+
+ // Verify that a sensor was created
+ expect(sensorId).not.toBeNull();
+
+ // Verify that only one sensor object element exists (for ResizeObserver fallback)
+ let sensorObjectCount = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.querySelectorAll('.size-sensor-object').length;
+ });
+
+ // Should be 0 or 1 (depends on if ResizeObserver is available)
+ expect(sensorObjectCount).toBeLessThanOrEqual(1);
+
+ // Call first unbind
+ await page.evaluate(() => {
+ window.unbind1();
+ });
+
+ // Verify sensor is still attached (because unbind2 still references it)
+ let stillHasSensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id') !== null;
+ });
+ expect(stillHasSensorId).toBe(true);
+
+ // Call second unbind
+ await page.evaluate(() => {
+ window.unbind2();
+ });
+
+ // Verify sensor is now removed
+ const finalSensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id');
+ });
+ expect(finalSensorId).toBeNull();
+
+ // Verify sensor object is also removed
+ sensorObjectCount = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.querySelectorAll('.size-sensor-object').length;
+ });
+ expect(sensorObjectCount).toBe(0);
+ });
+});
diff --git a/tests/unbind.spec.mjs b/tests/unbind.spec.mjs
new file mode 100644
index 0000000..eec79a7
--- /dev/null
+++ b/tests/unbind.spec.mjs
@@ -0,0 +1,75 @@
+import { test, expect } from '@playwright/test';
+import { readFileSync } from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Read the built UMD bundle
+const sizeSensorBundle = readFileSync(path.join(__dirname, '../dist/size-sensor.min.js'), 'utf8');
+
+test.describe('unbind', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.setContent('');
+ await page.addScriptTag({ content: sizeSensorBundle });
+ });
+
+ test('clear', async ({ page }) => {
+ await page.evaluate(() => {
+ const { bind, clear } = window.sizeSensor;
+ const wrapper = document.getElementById('wrapper');
+
+ bind(wrapper, () => {});
+ });
+
+ // Verify sensor-id is set
+ let sensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id');
+ });
+ expect(sensorId).not.toBeNull();
+
+ // Clear the sensor
+ await page.evaluate(() => {
+ const { clear } = window.sizeSensor;
+ const wrapper = document.getElementById('wrapper');
+ clear(wrapper);
+ });
+
+ // Verify sensor-id is removed
+ sensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id');
+ });
+ expect(sensorId).toBeNull();
+ });
+
+ test('unbind', async ({ page }) => {
+ await page.evaluate(() => {
+ const { bind } = window.sizeSensor;
+ const wrapper = document.getElementById('wrapper');
+
+ window.unbindFunc = bind(wrapper, () => {});
+ });
+
+ // Verify sensor-id is set
+ let sensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id');
+ });
+ expect(sensorId).not.toBeNull();
+
+ // Call unbind
+ await page.evaluate(() => {
+ window.unbindFunc();
+ });
+
+ // Verify sensor-id is removed
+ sensorId = await page.evaluate(() => {
+ const wrapper = document.getElementById('wrapper');
+ return wrapper.getAttribute('size-sensor-id');
+ });
+ expect(sensorId).toBeNull();
+ });
+});