return function()local Y=require(script.Parent.Parent.assertDeepEqual)local i=require(script.Parent.Parent.createSpy)local h=require(script.Parent.Parent.createElement)local x=require(script.Parent.Parent.createFragment)local C=require(script.Parent.Parent.createReconciler)local P=require(script.Parent.Parent.NoopRenderer)local R=require(script.Parent.Parent.Component)local K=C(P)it("\115\104\111\117\108\100 \098\101 \105\110\118\111\107\101\100 \111\110 \105\110\105\116\105\097\108 \109\111\117\110\116",function()local x=i()local C=R:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")C.getDerivedStateFromProps=x.value function C.render(Y)return nil end local P=h(C,{someProp=1})local A=nil local q="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"K.mountVirtualNode(P,A,q);(expect(x.callCount)).to.equal(1)local k=x:captureValues("\112\114\111\112\115","\115\116\097\116\101")Y(k.props,{someProp=1})Y(k.state,{})end)it("\115\104\111\117\108\100 \098\101 \105\110\118\111\107\101\100 \119\104\101\110 \117\112\100\097\116\101\100 \118\105\097 \112\114\111\112\115",function()local x=i()local C=R:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")C.getDerivedStateFromProps=x.value function C.render(Y)return nil end local P=nil local A="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local q=K.mountVirtualNode(h(C,{someProp=1}),P,A)K.updateVirtualNode(q,h(C,{someProp=2}));(expect(x.callCount)).to.equal(2)local k=x:captureValues("\112\114\111\112\115","\115\116\097\116\101")Y(k.props,{someProp=2})Y(k.state,{})end)it("\115\104\111\117\108\100 \098\101 \105\110\118\111\107\101\100 \119\104\101\110 \117\112\100\097\116\101\100 \118\105\097 \115\116\097\116\101",function()local x=i()local C=R:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")C.getDerivedStateFromProps=x.value function C.init(Y)Y:setState({someState=1})end function C.render(Y)return nil end local P=h(C)local A=nil local q="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local k=K.mountVirtualNode(P,A,q)K.updateVirtualNode(k,P,{someState=2});(expect(x.callCount)).to.equal(4)local H=x:captureValues("\112\114\111\112\115","\115\116\097\116\101")Y(H.props,{})Y(H.state,{someState=2})end)it("\115\104\111\117\108\100 \098\101 \105\110\118\111\107\101\100 \119\104\101\110 \117\112\100\097\116\105\110\103 \118\105\097 \115\116\097\116\101 \105\110 \105\110\105\116 \040\119\104\105\099\104 \115\107\105\112\115 \114\101\099\111\110\099\105\108\105\097\116\105\111\110\041",function()local x=i()local C=R:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")C.getDerivedStateFromProps=x.value function C.init(Y)Y:setState({stateFromInit=1})end function C.render(Y)return nil end local P=h(C,{someProp=1})local A=nil local q="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"K.mountVirtualNode(P,A,q);(expect(x.callCount)).to.equal(3)local k=x:captureValues("\112\114\111\112\115","\115\116\097\116\101")Y(k.props,{someProp=1})Y(k.state,{stateFromInit=1})end)it("\115\104\111\117\108\100 \114\101\099\101\105\118\101 \100\101\102\097\117\108\116\080\114\111\112\115",function()local x=i()local C=R:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")C.defaultProps={someDefaultProp="\102\111\111"}C.getDerivedStateFromProps=x.value function C.render(Y)return nil end local P=h(C,{someProp=1})local A=nil local q="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local k=K.mountVirtualNode(P,A,q);(expect(x.callCount)).to.equal(1)local H=x:captureValues("\112\114\111\112\115","\115\116\097\116\101")Y(H.props,{someDefaultProp="\102\111\111",someProp=1})P=h(C,{someProp=2})K.updateVirtualNode(k,P);(expect(x.callCount)).to.equal(2)H=x:captureValues("\112\114\111\112\115","\115\116\097\116\101")Y(H.props,{someDefaultProp="\102\111\111",someProp=2})end)it("\115\104\111\117\108\100 \100\101\114\105\118\101 \115\116\097\116\101 \102\111\114 \097\108\108 \115\101\116\083\116\097\116\101 \117\112\100\097\116\101\115\044 \101\118\101\110 \119\104\101\110 \100\101\102\101\114\114\101\100",function()local Y=R:extend("\067\104\105\108\100")local C=i(function()return{}end)local P=i()function Y.render(Y)return nil end function Y.didMount(Y)Y.props.callback()end local A=R:extend("\080\097\114\101\110\116")A.getDerivedStateFromProps=P.value function A.render(i)local P=function()i:setState(C.value)end return x({ChildA=h(Y,{callback=P}),ChildB=h(Y,{callback=P})})end local q=h(A)local k=nil local H="\084\101\115\116"K.mountVirtualNode(q,k,H);(expect(C.callCount)).to.equal(2);(expect(P.callCount)).to.equal(3)end)it("\115\104\111\117\108\100 \104\097\118\101 \100\101\114\105\118\101\100 \115\116\097\116\101 \097\102\116\101\114 \097\115\115\105\103\110\105\110\103 \116\111 \115\116\097\116\101 \105\110 \105\110\105\116",function()local x local C=i(function()return{derived=true}end)local P=R:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")P.getDerivedStateFromProps=C.value function P.init(Y)Y.state={init=true}x=function()return Y.state end end function P.render(Y)return nil end local A=nil local q="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local k=h(P)K.mountVirtualNode(k,A,q);(expect(C.callCount)).to.equal(2)Y(x(),{init=true;derived=true})end)end
return function()
	local assertDeepEqual = require(script.Parent.Parent.assertDeepEqual)
	local createSpy = require(script.Parent.Parent.createSpy)
	local createElement = require(script.Parent.Parent.createElement)
	local createFragment = require(script.Parent.Parent.createFragment)
	local createReconciler = require(script.Parent.Parent.createReconciler)
	local NoopRenderer = require(script.Parent.Parent.NoopRenderer)

	local Component = require(script.Parent.Parent.Component)

	local noopReconciler = createReconciler(NoopRenderer)

	it("should be invoked on initial mount", function()
		local getDerivedSpy = createSpy()
		local WithDerivedState = Component:extend("WithDerivedState")

		WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

		function WithDerivedState:render()
			return nil
		end

		local element = createElement(WithDerivedState, {
			someProp = 1,
		})
		local hostParent = nil
		local hostKey = "WithDerivedState"

		noopReconciler.mountVirtualNode(element, hostParent, hostKey)

		expect(getDerivedSpy.callCount).to.equal(1)

		local values = getDerivedSpy:captureValues("props", "state")

		assertDeepEqual(values.props, { someProp = 1 })
		assertDeepEqual(values.state, {})
	end)

	it("should be invoked when updated via props", function()
		local getDerivedSpy = createSpy()
		local WithDerivedState = Component:extend("WithDerivedState")

		WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

		function WithDerivedState:render()
			return nil
		end

		local hostParent = nil
		local hostKey = "WithDerivedState"

		local node = noopReconciler.mountVirtualNode(
			createElement(WithDerivedState, {
				someProp = 1,
			}),
			hostParent,
			hostKey
		)

		noopReconciler.updateVirtualNode(
			node,
			createElement(WithDerivedState, {
				someProp = 2,
			})
		)

		expect(getDerivedSpy.callCount).to.equal(2)

		local values = getDerivedSpy:captureValues("props", "state")

		assertDeepEqual(values.props, { someProp = 2 })
		assertDeepEqual(values.state, {})
	end)

	it("should be invoked when updated via state", function()
		local getDerivedSpy = createSpy()
		local WithDerivedState = Component:extend("WithDerivedState")

		WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

		function WithDerivedState:init()
			self:setState({
				someState = 1,
			})
		end

		function WithDerivedState:render()
			return nil
		end

		local element = createElement(WithDerivedState)
		local hostParent = nil
		local hostKey = "WithDerivedState"

		local node = noopReconciler.mountVirtualNode(element, hostParent, hostKey)

		noopReconciler.updateVirtualNode(node, element, {
			someState = 2,
		})

		-- getDerivedStateFromProps will be called:
		-- * Once on empty props
		-- * Once during the self:setState in init
		-- * Once more, defensively, on the resulting state AFTER init
		-- * On updating with new state via updateVirtualNode
		expect(getDerivedSpy.callCount).to.equal(4)

		local values = getDerivedSpy:captureValues("props", "state")

		assertDeepEqual(values.props, {})
		assertDeepEqual(values.state, { someState = 2 })
	end)

	it("should be invoked when updating via state in init (which skips reconciliation)", function()
		local getDerivedSpy = createSpy()
		local WithDerivedState = Component:extend("WithDerivedState")

		WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

		function WithDerivedState:init()
			self:setState({
				stateFromInit = 1,
			})
		end

		function WithDerivedState:render()
			return nil
		end

		local element = createElement(WithDerivedState, {
			someProp = 1,
		})
		local hostParent = nil
		local hostKey = "WithDerivedState"

		noopReconciler.mountVirtualNode(element, hostParent, hostKey)

		-- getDerivedStateFromProps will be called:
		-- * Once on empty props
		-- * Once during the self:setState in init
		-- * Once more, defensively, on the resulting state AFTER init
		expect(getDerivedSpy.callCount).to.equal(3)

		local values = getDerivedSpy:captureValues("props", "state")

		assertDeepEqual(values.props, {
			someProp = 1,
		})
		assertDeepEqual(values.state, {
			stateFromInit = 1,
		})
	end)

	it("should receive defaultProps", function()
		local getDerivedSpy = createSpy()
		local WithDerivedState = Component:extend("WithDerivedState")

		WithDerivedState.defaultProps = {
			someDefaultProp = "foo",
		}

		WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

		function WithDerivedState:render()
			return nil
		end

		local element = createElement(WithDerivedState, {
			someProp = 1,
		})
		local hostParent = nil
		local hostKey = "WithDerivedState"

		local node = noopReconciler.mountVirtualNode(element, hostParent, hostKey)

		expect(getDerivedSpy.callCount).to.equal(1)

		local values = getDerivedSpy:captureValues("props", "state")

		assertDeepEqual(values.props, {
			someDefaultProp = "foo",
			someProp = 1,
		})

		-- Update via props, confirm that defaultProp is still present
		element = createElement(WithDerivedState, {
			someProp = 2,
		})

		noopReconciler.updateVirtualNode(node, element)

		expect(getDerivedSpy.callCount).to.equal(2)

		values = getDerivedSpy:captureValues("props", "state")

		assertDeepEqual(values.props, {
			someDefaultProp = "foo",
			someProp = 2,
		})
	end)

	it("should derive state for all setState updates, even when deferred", function()
		local Child = Component:extend("Child")
		local stateUpdaterSpy = createSpy(function()
			return {}
		end)
		local stateDerivedSpy = createSpy()

		function Child:render()
			return nil
		end

		function Child:didMount()
			self.props.callback()
		end

		local Parent = Component:extend("Parent")

		Parent.getDerivedStateFromProps = stateDerivedSpy.value

		function Parent:render()
			local callback = function()
				self:setState(stateUpdaterSpy.value)
			end

			return createFragment({
				ChildA = createElement(Child, {
					callback = callback,
				}),
				ChildB = createElement(Child, {
					callback = callback,
				}),
			})
		end

		local element = createElement(Parent)
		local hostParent = nil
		local key = "Test"

		noopReconciler.mountVirtualNode(element, hostParent, key)

		expect(stateUpdaterSpy.callCount).to.equal(2)

		-- getDerivedStateFromProps is always called on initial state
		expect(stateDerivedSpy.callCount).to.equal(3)
	end)

	it("should have derived state after assigning to state in init", function()
		local getStateCallback
		local getDerivedSpy = createSpy(function()
			return {
				derived = true,
			}
		end)
		local WithDerivedState = Component:extend("WithDerivedState")

		WithDerivedState.getDerivedStateFromProps = getDerivedSpy.value

		function WithDerivedState:init()
			self.state = {
				init = true,
			}

			getStateCallback = function()
				return self.state
			end
		end

		function WithDerivedState:render()
			return nil
		end

		local hostParent = nil
		local hostKey = "WithDerivedState"
		local element = createElement(WithDerivedState)

		noopReconciler.mountVirtualNode(element, hostParent, hostKey)

		expect(getDerivedSpy.callCount).to.equal(2)

		assertDeepEqual(getStateCallback(), {
			init = true,
			derived = true,
		})
	end)
end
