local C=require(script.Parent.TestPlan)local K=require(script.Parent.TestEnum)local y={}y.__index=y function y.new()local K={plan=C.new(),nodeStack={};noXpcallByDefault=false,testNamePattern=nil}setmetatable(K,y)return K end function y.finalize(C)if#C.nodeStack~=0 then error("\067\097\110\110\111\116 \102\105\110\097\108\105\122\101 \097 \084\101\115\116\080\108\097\110 \119\105\116\104 \110\111\100\101\115 \115\116\105\108\108 \111\110 \116\104\101 \115\116\097\099\107\033",2)end return C.plan end function y.getCurrentNode(C)return C.nodeStack[#C.nodeStack]or C.plan end function y.pushNode(m,y,E,t)local S=m.nodeStack[#m.nodeStack]or m.plan local n for C,K in ipairs(S.children)do if K.phrase==y then n=K break end end if not n then n=C.createNode(y,E,t)n.parent=S table.insert(S.children,n)end table.insert(m.nodeStack,n)local d=n.modifier==nil or n.modifier==K.NodeModifier.None if m.testNamePattern and d then local C=n:getFullName()if C:match(m.testNamePattern)then n.modifier=K.NodeModifier.Focus else n.modifier=K.NodeModifier.Skip end end n.HACK_NO_XPCALL=m.noXpcallByDefault return n end function y.popNode(C)assert(#C.nodeStack>0,"\084\114\105\101\100 \116\111 \112\111\112 \102\114\111\109 \097\110 \101\109\112\116\121 \110\111\100\101 \115\116\097\099\107\033")return table.remove(C.nodeStack,#C.nodeStack)end return y
--[[
	Represents the ephermal state used for building a TestPlan from some other
	representation.

	TestPlanBuilder keeps track of a stack of nodes that represents the current
	position in the hierarchy, allowing the consumer to move up and down the
	tree as new nodes are discovered.
]]

local TestPlan = require(script.Parent.TestPlan)
local TestEnum = require(script.Parent.TestEnum)

local TestPlanBuilder = {}

TestPlanBuilder.__index = TestPlanBuilder

--[[
	Create a new TestPlanBuilder, used for creating a TestPlan.
]]
function TestPlanBuilder.new()
	local self = {
		plan = TestPlan.new(),
		nodeStack = {},
		noXpcallByDefault = false,
		testNamePattern = nil,
	}

	setmetatable(self, TestPlanBuilder)

	return self
end

--[[
	Verify that the TestPlanBuilder's state is valid and get a TestPlan from it.
]]
function TestPlanBuilder:finalize()
	if #self.nodeStack ~= 0 then
		error("Cannot finalize a TestPlan with nodes still on the stack!", 2)
	end

	return self.plan
end

--[[
	Grab the current node being worked on by the TestPlanBuilder.
]]
function TestPlanBuilder:getCurrentNode()
	return self.nodeStack[#self.nodeStack] or self.plan
end

--[[
	Creates and pushes a node onto the navigation stack.
]]
function TestPlanBuilder:pushNode(phrase, nodeType, nodeModifier)
	local lastNode = self.nodeStack[#self.nodeStack] or self.plan

	-- Find an existing node with this phrase to use
	local useNode
	for _, child in ipairs(lastNode.children) do
		if child.phrase == phrase then
			useNode = child
			break
		end
	end

	-- Didn't find one, create a new node
	if not useNode then
		useNode = TestPlan.createNode(phrase, nodeType, nodeModifier)
		useNode.parent = lastNode

		table.insert(lastNode.children, useNode)
	end

	table.insert(self.nodeStack, useNode)

	local nodeModifierNotSet = useNode.modifier == nil or useNode.modifier == TestEnum.NodeModifier.None
	if self.testNamePattern and nodeModifierNotSet then
		local fullName = useNode:getFullName()
		if fullName:match(self.testNamePattern) then
			useNode.modifier = TestEnum.NodeModifier.Focus
		else
			useNode.modifier = TestEnum.NodeModifier.Skip
		end
	end
	useNode.HACK_NO_XPCALL = self.noXpcallByDefault

	return useNode
end

--[[
	Pops a node off of the node navigation stack.
]]
function TestPlanBuilder:popNode()
	assert(#self.nodeStack > 0, "Tried to pop from an empty node stack!")
	return table.remove(self.nodeStack, #self.nodeStack)
end

return TestPlanBuilder