return function()local N=require(script.Parent.Parent.Parent.assertDeepEqual)local R=require(script.Parent.Parent.createSpy)local z=require(script.Parent.Parent.createElement)local V=require(script.Parent.Parent.createFragment)local x=require(script.Parent.Parent.createReconciler)local b=require(script.Parent.Parent.NoopRenderer)local q=require(script.Parent.Parent.Component)local K=x(b)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 V=R()local x=q:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")x.getDerivedStateFromProps=V.value function x.render(N)return nil end local b=z(x,{someProp=1})local w=nil local U="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"K.mountVirtualNode(b,w,U);(expect(V.callCount)).to.equal(1)local l=V:captureValues("\112\114\111\112\115","\115\116\097\116\101")N(l.props,{someProp=1})N(l.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 V=R()local x=q:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")x.getDerivedStateFromProps=V.value function x.render(N)return nil end local b=nil local w="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local U=K.mountVirtualNode(z(x,{someProp=1}),b,w)K.updateVirtualNode(U,z(x,{someProp=2}));(expect(V.callCount)).to.equal(2)local l=V:captureValues("\112\114\111\112\115","\115\116\097\116\101")N(l.props,{someProp=2})N(l.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 V=R()local x=q:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")x.getDerivedStateFromProps=V.value function x.init(N)N:setState({someState=1})end function x.render(N)return nil end local b=z(x)local w=nil local U="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local l=K.mountVirtualNode(b,w,U)K.updateVirtualNode(l,b,{someState=2});(expect(V.callCount)).to.equal(4)local k=V:captureValues("\112\114\111\112\115","\115\116\097\116\101")N(k.props,{})N(k.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 V=R()local x=q:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")x.getDerivedStateFromProps=V.value function x.init(N)N:setState({stateFromInit=1})end function x.render(N)return nil end local b=z(x,{someProp=1})local w=nil local U="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"K.mountVirtualNode(b,w,U);(expect(V.callCount)).to.equal(3)local l=V:captureValues("\112\114\111\112\115","\115\116\097\116\101")N(l.props,{someProp=1})N(l.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 V=R()local x=q:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")x.defaultProps={someDefaultProp="\102\111\111"}x.getDerivedStateFromProps=V.value function x.render(N)return nil end local b=z(x,{someProp=1})local w=nil local U="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local l=K.mountVirtualNode(b,w,U);(expect(V.callCount)).to.equal(1)local k=V:captureValues("\112\114\111\112\115","\115\116\097\116\101")N(k.props,{someDefaultProp="\102\111\111",someProp=1})b=z(x,{someProp=2})K.updateVirtualNode(l,b);(expect(V.callCount)).to.equal(2)k=V:captureValues("\112\114\111\112\115","\115\116\097\116\101")N(k.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 N=q:extend("\067\104\105\108\100")local x=R(function()return{}end)local b=R()function N.render(N)return nil end function N.didMount(N)N.props.callback()end local w=q:extend("\080\097\114\101\110\116")w.getDerivedStateFromProps=b.value function w.render(R)local b=function()R:setState(x.value)end return V({ChildA=z(N,{callback=b});ChildB=z(N,{callback=b})})end local U=z(w)local l=nil local k="\084\101\115\116"K.mountVirtualNode(U,l,k);(expect(x.callCount)).to.equal(2);(expect(b.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 V local x=R(function()return{derived=true}end)local b=q:extend("\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101")b.getDerivedStateFromProps=x.value function b.init(N)N.state={init=true}V=function()return N.state end end function b.render(N)return nil end local w=nil local U="\087\105\116\104\068\101\114\105\118\101\100\083\116\097\116\101"local l=z(b)K.mountVirtualNode(l,w,U);(expect(x.callCount)).to.equal(2)N(V(),{init=true;derived=true})end)end
return function()
	local assertDeepEqual = require(script.Parent.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
