/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { act } from 'react-dom/test-utils'

import useStable from '..'

function createRoot(rootNode) {
  return {
    render(element) {
      ReactDOM.render(element, rootNode)
    },
  }
}

describe('useStable', () => {
  let root
  let rootNode
  let spy = {}

  const TestComponent = ({ initialValueCallback }): React.ReactNode => {
    const value = useStable(initialValueCallback)
    spy.value = value
    return null
  }

  beforeEach(() => {
    spy = {}
    rootNode = document.createElement('div')
    document.body.appendChild(rootNode)
    root = createRoot(rootNode)
  })

  afterEach(() => {
    root.render(null)
    document.body.removeChild(rootNode)
    rootNode = null
    root = null
  })

  test('correctly sets the initial value', () => {
    const initialValueCallback = () => 5
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(spy.value).toBe(5)
  })

  test('does not change the value', () => {
    let counter = 0
    const initialValueCallback = () => counter++
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(spy.value).toBe(0)
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(spy.value).toBe(0)
  })

  test('only calls the callback once', () => {
    let counter = 0
    const initialValueCallback = () => counter++
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(counter).toBe(1)
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(counter).toBe(1)
  })

  test('does not change the value if the callback initially returns null', () => {
    let counter = 0
    const initialValueCallback = () => {
      if (counter === 0) {
        counter++
        return null
      }
      return counter++
    }
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(spy.value).toBe(null)
    act(() => {
      root.render(<TestComponent initialValueCallback={initialValueCallback} />)
    })
    expect(spy.value).toBe(null)
  })
})
