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(); + }); +});