Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
23 changes: 23 additions & 0 deletions e2e/tests/shared/parent-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ export function parentMethodTests(baseUrl, { hasDisconnect = true } = {}) {
)
})

test('connectResizer re-binding sends update without breaking the iframe', async ({
page,
}) => {
await page.goto(baseUrl)
await page.waitForLoadState('networkidle')
await waitForResizer(page)

// Skip frameworks that don't expose the imperative factory globally.
const hasFactory = await page.evaluate(
() => typeof window.iframeResize === 'function',
)
test.skip(!hasFactory, 'iframeResize factory not exposed globally')

// Re-bind with new options on an already-connected iframe.
// The update flow should fire (no throw) and iframeResizer stays attached.
const result = await page.evaluate(() => {
const iframe = document.querySelector('iframe')
window.iframeResize({ license: 'GPLv3', log: true }, iframe)
return iframe.iframeResizer ? 'attached' : 'detached'
})
expect(result).toBe('attached')
})

if (hasDisconnect) {
test('disconnect removes iframeResizer from iframe', async ({ page }) => {
await page.goto(baseUrl)
Expand Down
38 changes: 27 additions & 11 deletions packages/angular/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
EventEmitter,
Input,
Output,
type SimpleChanges,
} from '@angular/core'
import { esModuleInterop } from '@iframe-resizer/common'
import type {
Expand Down Expand Up @@ -63,16 +64,8 @@ export class IframeResizerDirective {

constructor(private elementRef: ElementRef) {}

ngAfterViewInit(): void {
const id = this.elementRef.nativeElement?.id

this.consoleGroup.label(`angular(${id})`)
this.consoleGroup.event('setup')
this.consoleGroup.expand(this.options.logExpand)

if (this.debug) this.consoleGroup.log('ngAfterViewInit')

this.resizer = connectResizer({
private buildOptions(): IFrameOptions {
return {
...this.options,

onBeforeClose: () => {
Expand All @@ -98,7 +91,30 @@ export class IframeResizerDirective {
top: number
left: number
}) => this.onScroll.next(event),
})(this.elementRef.nativeElement)
} as IFrameOptions
}

ngAfterViewInit(): void {
const id = this.elementRef.nativeElement?.id

this.consoleGroup.label(`angular(${id})`)
this.consoleGroup.event('setup')
this.consoleGroup.expand(this.options.logExpand)

if (this.debug) this.consoleGroup.log('ngAfterViewInit')

this.resizer = connectResizer(this.buildOptions())(
this.elementRef.nativeElement,
)
}

ngOnChanges(changes: SimpleChanges): void {
// Re-bind when @Input options change. Skip the first call: the binding
// hasn't been established yet at that point — ngAfterViewInit handles it.
if (!this.resizer) return
if (!changes.options) return
if (this.debug) this.consoleGroup.log('ngOnChanges: options updated')
connectResizer(this.buildOptions())(this.elementRef.nativeElement)
}

ngOnDestroy(): void {
Expand Down
12 changes: 12 additions & 0 deletions packages/child/events/mouse.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'

import sendMessage from '../send/message'
import settings from '../values/settings'
import setupMouseEvents from './mouse'

vi.mock('../send/message', () => ({ default: vi.fn() }))

describe('child/events/mouse', () => {
let addSpy
beforeEach(() => {
settings.mouseEvents = true
addSpy = vi.spyOn(document, 'addEventListener')
})

Expand Down Expand Up @@ -37,4 +39,14 @@ describe('child/events/mouse', () => {
expect(sendMessage).toHaveBeenCalledWith(0, 0, 'mouseenter', '10:20')
expect(sendMessage).toHaveBeenCalledWith(0, 0, 'mouseleave', '30:40')
})

test('does not forward events when settings.mouseEvents is false', () => {
setupMouseEvents({ mouseEvents: true })

settings.mouseEvents = false
const [, fn1] = addSpy.mock.calls[0].slice(0, 2)
fn1({ type: 'mouseenter', screenY: 10, screenX: 20 })

expect(sendMessage).not.toHaveBeenCalled()
})
})
5 changes: 4 additions & 1 deletion packages/child/events/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { HIGHLIGHT } from 'auto-console-group'

import { log } from '../console'
import sendMessage from '../send/message'
import settings from '../values/settings'
import { addEventListener } from './listeners'

const sendMouse = (evt: MouseEvent): void =>
const sendMouse = (evt: MouseEvent): void => {
if (settings.mouseEvents !== true) return
sendMessage(0, 0, evt.type, `${evt.screenY}:${evt.screenX}`)
}

function addMouseListener(evt: string, name: string): void {
log(`Add event listener: %c${name}`, HIGHLIGHT)
Expand Down
1 change: 1 addition & 0 deletions packages/child/page/links.branches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('child/page/links branches', () => {
document.body.innerHTML = ''
state.inPageLinks = undefined
settings.mode = 0
settings.inPageLinks = true
sendMessage.mockClear()
})

Expand Down
11 changes: 10 additions & 1 deletion packages/child/page/links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'
import sendMessage from '../send/message'
import settings from '../values/settings'
import state from '../values/state'
import setupInPageLinks from './links'
import setupInPageLinks, { findTarget } from './links'

vi.mock('../console', () => ({ log: vi.fn(), advise: vi.fn() }))
vi.mock('../send/message', () => ({ __esModule: true, default: vi.fn() }))
Expand All @@ -13,6 +13,8 @@ describe('child/page/links', () => {
vi.clearAllMocks()
document.body.innerHTML = ''
settings.mode = 0
settings.inPageLinks = true
state.inPageLinks = undefined
})

test('setup and findTarget sends message for existing id', () => {
Expand All @@ -30,4 +32,11 @@ describe('child/page/links', () => {

expect(sendMessage).toHaveBeenCalled()
})

test('findTarget no-op when inPageLinks setting becomes false', () => {
settings.inPageLinks = false
findTarget('#nope')

expect(sendMessage).not.toHaveBeenCalled()
})
})
22 changes: 14 additions & 8 deletions packages/child/page/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ function jumpToTarget(hash: string, target: Element): void {
}

export function findTarget(location: string): void {
if (settings.inPageLinks !== true) return

const hash = location.split('#')[1] || location // Remove # if present
const hashData = decodeURIComponent(hash)
const target =
Expand Down Expand Up @@ -98,16 +100,20 @@ function enableInPageLinks(): void {
}
}

export default function setupInPageLinks(enabled: boolean): void {
export default function setupInPageLinks(requested: boolean): void {
const { mode } = settings

if (enabled) {
if (checkMode(mode)) {
advise(getModeData(5))
} else {
enableInPageLinks()
}
} else {
if (!requested) {
log('In page linking not enabled')
return
}

if (state.inPageLinks?.findTarget) return // Already wired up

if (checkMode(mode)) {
advise(getModeData(5))
return
}

enableInPageLinks()
}
2 changes: 1 addition & 1 deletion packages/child/page/links.units.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ vi.mock('../console', () => ({ advise: vi.fn(), log: vi.fn() }))
vi.mock('../send/message', () => ({ __esModule: true, default: vi.fn() }))
vi.mock('../events/listeners', () => ({ addEventListener: vi.fn() }))
vi.mock('../values/settings', () => ({
default: { mode: 0 },
default: { mode: 0, inPageLinks: true },
}))
vi.mock('../values/state', () => ({
default: { inPageLinks: null },
Expand Down
2 changes: 2 additions & 0 deletions packages/child/received/process-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import pageInfo from './page-info'
import parentInfo from './parent-info'
import reset from './reset'
import resize from './resize'
import update from './update'
import { getData } from './utils'

const moveToAnchor = (event: MessageEvent): void =>
Expand All @@ -19,4 +20,5 @@ export default {
pageInfo,
parentInfo,
message,
update,
}
124 changes: 124 additions & 0 deletions packages/child/received/update.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

import settings from '../values/settings'

vi.mock('../console', () => ({
errorBoundary: (fn: any) => fn,
event: vi.fn(),
log: vi.fn(),
setConsoleOptions: vi.fn(),
warn: vi.fn(),
}))
vi.mock('../events/mouse', () => ({ default: vi.fn() }))
vi.mock('../page/css', () => ({
setBodyStyle: vi.fn(),
setMargin: vi.fn(),
}))
vi.mock('../page/links', () => ({ default: vi.fn() }))

const updateFromParent = (await import('./update')).default
const consoleMod = await import('../console')
const setupMouseEvents = (await import('../events/mouse')).default
const cssMod = await import('../page/css')
const setupInPageLinks = (await import('../page/links')).default

const buildEvent = (id: string, fields: Record<string, any>): MessageEvent => {
// Wire format mirrors createOutgoingMessage on the parent side, prefixed
// with the iframeResizer header and the "update:" event type.
const data = [
id,
'8',
fields.sizeWidth ?? 'false',
fields.logging ?? 'false',
'32',
'true',
fields.autoResize ?? 'true',
'0',
'auto',
'',
'',
fields.tolerance ?? '0',
fields.inPageLinks ?? 'false',
'child',
'auto',
fields.mouseEvents ?? 'false',
'0',
'0',
fields.sizeHeight ?? 'true',
'',
'',
'0',
'',
fields.logExpand ?? 'false',
].join(':')
return { data: `[iFrameSizer]update:${data}` } as MessageEvent
}

describe('child/received/update', () => {
beforeEach(() => {
vi.clearAllMocks()
settings.mouseEvents = false
settings.inPageLinks = false
settings.logging = false
settings.bodyBackground = ''
settings.bodyMarginStr = ''
settings.bodyPadding = ''
})

it('updates console settings on every update', () => {
const event = buildEvent('edge1', {
logging: 'true',
logExpand: 'true',
})
updateFromParent(event)

expect(settings.logging).toBe(true)
expect(settings.logExpand).toBe(true)
expect(consoleMod.setConsoleOptions).toHaveBeenCalledWith({
id: settings.parentId,
enabled: true,
expand: true,
})
})

it('does not touch body styles when they are unchanged', () => {
// Match the values the buildEvent fixture will deliver so previous === incoming
settings.bodyBackground = ''
settings.bodyMarginStr = '0'
settings.bodyPadding = ''

updateFromParent(buildEvent('edge1', {}))

expect(cssMod.setMargin).not.toHaveBeenCalled()
expect(cssMod.setBodyStyle).not.toHaveBeenCalled()
})

it('calls setupMouseEvents only when mouseEvents transitions false → true', () => {
settings.mouseEvents = false

updateFromParent(buildEvent('edge1', { mouseEvents: 'true' }))
expect(setupMouseEvents).toHaveBeenCalledTimes(1)

// already true, second update with the same value should not re-attach
updateFromParent(buildEvent('edge1', { mouseEvents: 'true' }))
expect(setupMouseEvents).toHaveBeenCalledTimes(1)
})

it('does not call setupMouseEvents when mouseEvents stays false', () => {
settings.mouseEvents = false

updateFromParent(buildEvent('edge1', { mouseEvents: 'false' }))
expect(setupMouseEvents).not.toHaveBeenCalled()
})

it('calls setupInPageLinks only when inPageLinks transitions false → true', () => {
settings.inPageLinks = false

updateFromParent(buildEvent('edge1', { inPageLinks: 'true' }))
expect(setupInPageLinks).toHaveBeenCalledWith(true)
expect(setupInPageLinks).toHaveBeenCalledTimes(1)

updateFromParent(buildEvent('edge1', { inPageLinks: 'true' }))
expect(setupInPageLinks).toHaveBeenCalledTimes(1)
})
})
Loading