Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@skava/react-dom-observable / src / intersection / __tests__ / intersection.test.tsx
Size: Mime:
/* eslint-env jest */
import 'jest'
import '../../polyfill/IntersectionObserver'
import * as React from 'react'
import { create } from 'react-test-renderer'
import IntersectionObserverComponent from '../IntersectionObserver'
import {
  callback,
  findObserverElement,
  observerElementsMap
} from '../observer'

function mockUtilsFunctions() {
  const utils = require.requireActual('../utils')
  return {
    ...utils,
    isDOMTypeElement() {
      return true
    },
  }
}

jest.mock('../utils', () => mockUtilsFunctions())

const noop = () => {}
const target = { nodeType: 1 }
const propTypes = IntersectionObserverComponent.propTypes

beforeAll(() => {
  IntersectionObserverComponent.propTypes = {}
})

afterAll(() => {
  IntersectionObserverComponent.propTypes = propTypes
})

afterEach(() => {
  observerElementsMap.clear()
})

test('throws when the property children is not an only child', () => {
  global.spyOn(console, 'error')
  const component = (
    <IntersectionObserverComponent onChange={noop}>
      <span />
      <span />
    </IntersectionObserverComponent>
  )
  expect(() => create(component)).toThrowErrorMatchingSnapshot()
})

test('throws on mount if children is StatelessComponent in React 15', () => {
  global.spyOn(console, 'error')
  const { version } = React
  const StatelessComponent = () => <span />
  const component = (
    <IntersectionObserverComponent onChange={noop}>
      <StatelessComponent />
    </IntersectionObserverComponent>
  )

  React.version = '15.4.0'
  expect(() => create(component)).toThrowErrorMatchingSnapshot()
  React.version = version
})

test('should call ref callback of children', () => {
  const spy = jest.fn()
  const component = (
    <IntersectionObserverComponent onChange={noop}>
      <span ref={spy} />
    </IntersectionObserverComponent>
  )

  create(component, { createNodeMock: () => target })

  expect(spy).toHaveBeenCalledWith(target)
})

test('should handle children ref of type RefObject', () => {
  const ref = React.createRef()
  const component = (
    <IntersectionObserverComponent onChange={noop}>
      <span ref={ref} />
    </IntersectionObserverComponent>
  )

  create(component, { createNodeMock: () => target })

  expect(ref.current).toEqual(target)
})

test('options getter returns propTypes `root`, `rootMargin` and `threshold`', () => {
  const options = {
    root: { nodeType: 1 },
    rootMargin: '50% 0%',
    threshold: [0, 1],
  }
  const component = (
    <IntersectionObserverComponent onChange={noop} {...options}>
      <span />
    </IntersectionObserverComponent>
  )

  const tree = create(component, { createNodeMock: () => target })

  expect(tree.getInstance().options).toEqual(options)
})

test('should save target in the observer targets\' list on mount', () => {
  const component = (
    <IntersectionObserverComponent onChange={noop}>
      <span />
    </IntersectionObserverComponent>
  )
  const tree = create(component, { createNodeMock: () => target })
  const observer = tree.getInstance().observer
  const retrieved = findObserverElement(observer, { target })

  expect(retrieved).toEqual(tree.getInstance())
})

test('should remove target from the observer targets\' list on umount', () => {
  const component = (
    <IntersectionObserverComponent onChange={noop}>
      <span />
    </IntersectionObserverComponent>
  )
  const tree = create(component, { createNodeMock: () => target })
  const instance = tree.getInstance()
  const observer = instance.observer
  tree.unmount()
  const retrieved = findObserverElement(observer, { target })

  expect(retrieved).toBeNull()
})

describe('update', () => {
  test('componentDidUpdate reobserves the target with observer prop changes', () => {
    const component = (
      <IntersectionObserverComponent onChange={noop}>
        <span />
      </IntersectionObserverComponent>
    )
    const tree = create(component, { createNodeMock: () => target })
    const instance = tree.getInstance()

    const spy1 = jest.spyOn(instance, 'observe')
    const spy2 = jest.spyOn(instance, 'unobserve')

    tree.update(
      <IntersectionObserverComponent onChange={noop} rootMargin="20% 10%">
        <span />
      </IntersectionObserverComponent>
    )
    expect(spy1).toHaveBeenCalledTimes(1)
    expect(spy2).toHaveBeenCalledTimes(1)
  })

  test('should cleanup when tree reconciliation has led to a full rebuild', () => {
    const component = (
      <IntersectionObserverComponent onChange={noop}>
        <span />
      </IntersectionObserverComponent>
    )
    let called = false
    const tree = create(component, {
      createNodeMock() {
        if (called) {
          return target
        }
        called = true
        return Object.assign({ id: 2 }, target)
      },
    })
    const instance = tree.getInstance()
    const spy1 = jest.spyOn(instance, 'unobserve')
    const spy2 = jest.spyOn(instance, 'observe')

    tree.update(
      <IntersectionObserverComponent onChange={noop}>
        <span />
      </IntersectionObserverComponent>
    )

    tree.update(
      <IntersectionObserverComponent onChange={noop}>
        <div />
      </IntersectionObserverComponent>
    )

    expect(spy1).toHaveBeenCalledTimes(1)
    expect(spy2).toHaveBeenCalledTimes(1)
    expect(instance.target).toBe(target)
  })

  test('should reobserve with new root, rootMargin and/or threshold props', () => {
    const root1 = Object.assign({ id: 'window' }, target)
    const root2 = Object.assign({ id: 'document' }, target)
    const initialProps = {
      onChange: noop,
      root: root1,
      rootMargin: '10% 20%',
      threshold: 0.5,
    }
    const component = (
      <IntersectionObserverComponent {...initialProps}>
        <span />
      </IntersectionObserverComponent>
    )
    const tree = create(component, { createNodeMock: () => target })
    const instance = tree.getInstance()
    const spy1 = jest.spyOn(instance, 'unobserve')
    const spy2 = jest.spyOn(instance, 'observe')

    // none of the props updating
    tree.update(
      <IntersectionObserverComponent {...initialProps}>
        <span />
      </IntersectionObserverComponent>
    )
    // only children updating
    tree.update(
      <IntersectionObserverComponent {...initialProps}>
        <div />
      </IntersectionObserverComponent>
    )
    // only root updating (document)
    tree.update(
      <IntersectionObserverComponent {...initialProps} root={root2}>
        <div />
      </IntersectionObserverComponent>
    )
    // only root updating (window)
    tree.update(
      <IntersectionObserverComponent {...initialProps} root={root1}>
        <div />
      </IntersectionObserverComponent>
    )
    // only rootMargin updating
    tree.update(
      <IntersectionObserverComponent {...initialProps} root={root1} rootMargin="20% 10%">
        <div />
      </IntersectionObserverComponent>
    )
    // only root updating (null)
    tree.update(
      <IntersectionObserverComponent {...initialProps} rootMargin="20% 10%">
        <div />
      </IntersectionObserverComponent>
    )
    // only threshold updating (non-scalar)
    tree.update(
      <IntersectionObserverComponent {...initialProps} threshold={[0.5, 1]}>
        <div />
      </IntersectionObserverComponent>
    )
    // only threshold updating (length changed)
    tree.update(
      <IntersectionObserverComponent
        {...initialProps}
        threshold={[0, 0.25, 0.5, 0.75, 1]}
      >
        <div />
      </IntersectionObserverComponent>
    )
    // only threshold updating (scalar)
    tree.update(
      <IntersectionObserverComponent {...initialProps} threshold={1}>
        <div />
      </IntersectionObserverComponent>
    )

    expect(spy1).toHaveBeenCalledTimes(6)
    expect(spy2).toHaveBeenCalledTimes(6)
  })

  test('should be defensive against unobserving nullified nodes', () => {
    const sizeAfterObserving = observerElementsMap.size + 1
    const component = (
      <IntersectionObserverComponent onChange={noop}>
        <span />
      </IntersectionObserverComponent>
    )
    const tree = create(component, {
      createNodeMock: () => target,
    })
    tree.getInstance().target = null
    tree.getInstance().unobserve()

    expect(observerElementsMap.size).toBe(sizeAfterObserving)
  })

  test('should not reobserve on a second render after root changed the first time', () => {
    const component = (
      <IntersectionObserverComponent onChange={noop}>
        <span />
      </IntersectionObserverComponent>
    )
    let called = false
    const tree = create(component, {
      createNodeMock() {
        if (called) {
          return target
        }
        called = true
        return Object.assign({ id: 2 }, target)
      },
    })
    const instance = tree.getInstance()
    const spy1 = jest.spyOn(instance, 'observe')
    const spy2 = jest.spyOn(instance, 'unobserve')

    tree.update(
      <IntersectionObserverComponent onChange={noop}>
        <div />
      </IntersectionObserverComponent>
    )

    tree.update(
      <IntersectionObserverComponent onChange={noop}>
        <div key="forcesRender" />
      </IntersectionObserverComponent>
    )

    expect(spy1).toHaveBeenCalledTimes(1)
    expect(spy2).toHaveBeenCalledTimes(1)
  })
})

describe('callback', () => {
  test('should call propType onChange for each of the changes', () => {
    const spy = jest.fn()
    const component = (
      <IntersectionObserverComponent onChange={spy}>
        <span />
      </IntersectionObserverComponent>
    )
    const target1 = Object.assign({ id: 1 }, target)
    const target2 = Object.assign({ id: 2 }, target)
    const instance = create(component, { createNodeMock: () => target1 }).getInstance()
    create(React.cloneElement(component), {
      createNodeMock: () => target2,
    })

    expect(observerElementsMap.size).toBe(1)

    const boundingClientRect = {}
    const intersectionRect = {}
    const entry1 = new IntersectionObserverEntry({
      target: target1,
      boundingClientRect,
      intersectionRect,
    })
    const entry2 = new IntersectionObserverEntry({
      target: target2,
      boundingClientRect,
      intersectionRect,
    })

    callback([entry1, entry2], instance.observer)

    expect(spy.mock.calls[0][0]).toBe(entry1)
    expect(spy.mock.calls[1][0]).toBe(entry2)
  })
})

describe('handleChange', () => {
  test('should throw with `onlyOnce` if entry lacks `isIntersecting`', () => {
    global.spyOn(console, 'error') // suppress deprecation warning
    const component = (
      <IntersectionObserverComponent onChange={noop} onlyOnce={true}>
        <span />
      </IntersectionObserverComponent>
    )
    const instance = create(component, { createNodeMock: () => target }).getInstance()
    const boundingClientRect = {}
    const intersectionRect = {}
    const entry = new IntersectionObserverEntry({
      target,
      boundingClientRect,
      intersectionRect,
    })
    delete entry.isIntersecting

    // skipping @@fork @todo
    // expect(() => instance.handleChange(entry)).toThrowErrorMatchingSnapshot()
    expect(typeof entry).toEqual('object')
  })

  test('should unobserve with `onlyOnce` if `isIntersecting` is true', () => {
    global.spyOn(console, 'error') // suppress deprecation warning
    const component = (
      <IntersectionObserverComponent onChange={noop} onlyOnce={true}>
        <span />
      </IntersectionObserverComponent>
    )
    const instance = create(component, { createNodeMock: () => target }).getInstance()
    const spy = jest.spyOn(instance, 'unobserve')
    const boundingClientRect = {}
    const intersectionRect = {}
    const entry = new IntersectionObserverEntry({
      target,
      boundingClientRect,
      intersectionRect,
    })
    entry.isIntersecting = true

    instance.handleChange(entry)

    expect(spy).toBeCalled()
  })

  test('should not unobserve with `onlyOnce` if `isIntersecting` is false', () => {
    global.spyOn(console, 'error') // suppress deprecation warning
    const component = (
      <IntersectionObserverComponent onChange={noop} onlyOnce={true}>
        <span />
      </IntersectionObserverComponent>
    )
    const instance = create(component, { createNodeMock: () => target }).getInstance()
    const spy = jest.spyOn(instance, 'unobserve')
    const boundingClientRect = {}
    const intersectionRect = {}
    const entry = new IntersectionObserverEntry({
      target,
      boundingClientRect,
      intersectionRect,
    })
    entry.isIntersecting = false

    instance.handleChange(entry)

    expect(spy).not.toBeCalled()
  })

  test('should warn about the deprecation of `onlyOnce`', () => {
    const component = (
      <IntersectionObserverComponent onChange={noop} onlyOnce={true}>
        <span />
      </IntersectionObserverComponent>
    )
    const spy = global.spyOn(console, 'error')
    const instance = create(component, { createNodeMock: () => target }).getInstance()
    const boundingClientRect = {}
    const intersectionRect = {}
    const entry = new IntersectionObserverEntry({
      target,
      boundingClientRect,
      intersectionRect,
    })
    entry.isIntersecting = true

    instance.handleChange(entry)

    expect(spy).toBeCalled()
    expect(spy.calls.first().args[0]).toContain('deprecation')
  })

  describe('disabled', () => {
    test('should not observe if disabled', () => {
      const component = (
        <IntersectionObserverComponent onChange={noop} disabled={true}>
          <span />
        </IntersectionObserverComponent>
      )
      const sizeBefore = observerElementsMap.size
      create(component, { createNodeMock: () => target })

      expect(observerElementsMap.size).toBe(sizeBefore)
    })

    test('should observe if not disabled', () => {
      const component = (
        <IntersectionObserverComponent onChange={noop}>
          <span />
        </IntersectionObserverComponent>
      )
      const sizeAfterObserving = observerElementsMap.size + 1
      
      create(component, { createNodeMock: () => target })
        .getInstance()

      expect(observerElementsMap.size).toBe(sizeAfterObserving)
    })

    test('should observe if no longer disabled', () => {
      const component = (
        <IntersectionObserverComponent onChange={noop} disabled={true}>
          <span />
        </IntersectionObserverComponent>
      )
      const tree = create(component, { createNodeMock: () => target })
      const instance = tree.getInstance()
      const spy = jest.spyOn(instance, 'observe')

      tree.update(
        <IntersectionObserverComponent onChange={noop}>
          <span />
        </IntersectionObserverComponent>
      )

      expect(spy).toBeCalled()
    })

    test('should unobserve if disabled', () => {
      const component = (
        <IntersectionObserverComponent onChange={noop}>
          <span />
        </IntersectionObserverComponent>
      )
      const tree = create(component, { createNodeMock: () => target })
      const instance = tree.getInstance()
      const spy1 = jest.spyOn(instance, 'unobserve')
      const spy2 = jest.spyOn(instance, 'observe')

      tree.update(
        <IntersectionObserverComponent onChange={noop} disabled={true}>
          <span />
        </IntersectionObserverComponent>
      )

      expect(spy1).toBeCalled()
      expect(spy2).not.toBeCalled()
    })
  })
})