Skip to content
This repository was archived by the owner on Dec 13, 2023. It is now read-only.

Commit a1f914f

Browse files
Add snapshot serialization
1 parent 8450335 commit a1f914f

15 files changed

+1324
-12
lines changed

src/init.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local GlobalConfig = require(script.GlobalConfig)
66
local createReconciler = require(script.createReconciler)
77
local createReconcilerCompat = require(script.createReconcilerCompat)
88
local RobloxRenderer = require(script.RobloxRenderer)
9+
local shallow = require(script.shallow)
910
local strict = require(script.strict)
1011
local Binding = require(script.Binding)
1112

@@ -39,6 +40,8 @@ local Roact = strict {
3940

4041
setGlobalConfig = GlobalConfig.set,
4142

43+
shallow = shallow,
44+
4245
-- APIs that may change in the future without warning
4346
UNSTABLE = {
4447
},

src/init.spec.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ return function()
1313
update = "function",
1414
oneChild = "function",
1515
setGlobalConfig = "function",
16+
shallow = "function",
1617

1718
-- These functions are deprecated and throw warnings!
1819
reify = "function",

src/shallow.lua

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local Children = require(script.Parent.PropMarkers.Children)
33
local RobloxRenderer = require(script.Parent.RobloxRenderer)
44
local ElementKind = require(script.Parent.ElementKind)
55
local ElementUtils = require(script.Parent.ElementUtils)
6+
local snapshot = require(script.Parent.snapshot)
67

78
local robloxReconciler = createReconciler(RobloxRenderer)
89

@@ -50,18 +51,19 @@ local function findNextVirtualNode(virtualNode, maxDepth)
5051
end
5152

5253
local ContraintFunctions = {
53-
kind = function(element, expectKind)
54-
return ElementKind.of(element) == expectKind
54+
kind = function(virtualNode, expectKind)
55+
return ElementKind.of(virtualNode.currentElement) == expectKind
5556
end,
56-
className = function(element, className)
57+
className = function(virtualNode, className)
58+
local element = virtualNode.currentElement
5759
local isHost = ElementKind.of(element) == ElementKind.Host
5860
return isHost and element.component == className
5961
end,
60-
component = function(element, expectComponentValue)
61-
return element.component == expectComponentValue
62+
component = function(virtualNode, expectComponentValue)
63+
return virtualNode.currentElement.component == expectComponentValue
6264
end,
63-
props = function(element, propSubSet)
64-
local elementProps = element.props
65+
props = function(virtualNode, propSubSet)
66+
local elementProps = virtualNode.currentElement.props
6567

6668
for propKey, propValue in pairs(propSubSet) do
6769
if elementProps[propKey] ~= propValue then
@@ -70,7 +72,10 @@ local ContraintFunctions = {
7072
end
7173

7274
return true
73-
end
75+
end,
76+
hostKey = function(virtualNode, expectHostKey)
77+
return virtualNode.hostKey == expectHostKey
78+
end,
7479
}
7580

7681
local function countChildrenOfElement(element)
@@ -111,7 +116,7 @@ local function filterProps(props)
111116

112117
for key, value in pairs(props) do
113118
if key ~= Children then
114-
props[key] = value
119+
filteredProps[key] = value
115120
end
116121
end
117122

@@ -128,6 +133,8 @@ function ShallowWrapper.new(virtualNode, maxDepth)
128133
_shallowChildren = nil,
129134
type = getTypeFromVirtualNode(virtualNode),
130135
props = filterProps(virtualNode.currentElement.props),
136+
hostKey = virtualNode.hostKey,
137+
instance = virtualNode.hostObject,
131138
}
132139

133140
return setmetatable(wrapper, ShallowWrapperMetatable)
@@ -145,7 +152,7 @@ function ShallowWrapper:childrenCount()
145152
end
146153

147154
function ShallowWrapper:find(constraints)
148-
for constraint in pairs(constraints) do
155+
for constraint in pairs(constraints) do
149156
if not ContraintFunctions[constraint] then
150157
error(('unknown constraint %q'):format(constraint))
151158
end
@@ -164,6 +171,27 @@ function ShallowWrapper:find(constraints)
164171
return results
165172
end
166173

174+
function ShallowWrapper:findUnique(constraints)
175+
local children = self:getChildren()
176+
177+
if constraints == nil then
178+
assert(
179+
#children == 1,
180+
("expect to contain exactly one child, but found %d"):format(#children)
181+
)
182+
return children[1]
183+
end
184+
185+
local constrainedChildren = self:find(constraints)
186+
187+
assert(
188+
#constrainedChildren == 1,
189+
("expect to find only one child, but found %d"):format(#constrainedChildren)
190+
)
191+
192+
return constrainedChildren[1]
193+
end
194+
167195
function ShallowWrapper:getChildren()
168196
if self._shallowChildren then
169197
return self._shallowChildren
@@ -179,13 +207,21 @@ function ShallowWrapper:getChildren()
179207
return results
180208
end
181209

210+
function ShallowWrapper:toMatchSnapshot(identifier)
211+
assert(typeof(identifier) == "string", "Snapshot identifier must be a string")
212+
213+
local snapshotResult = snapshot(identifier, self)
214+
215+
snapshotResult:match()
216+
end
217+
182218
function ShallowWrapper:_satisfiesAllContraints(constraints)
183-
local element = self._virtualNode.currentElement
219+
local virtualNode = self._virtualNode
184220

185221
for constraint, value in pairs(constraints) do
186222
local constraintFunction = ContraintFunctions[constraint]
187223

188-
if not constraintFunction(element, value) then
224+
if not constraintFunction(virtualNode, value) then
189225
return false
190226
end
191227
end

src/shallow.spec.lua

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,37 @@ return function()
379379
end)
380380
end)
381381

382+
describe("instance", function()
383+
it("should contain the instance when it is a host component", function()
384+
local className = "Frame"
385+
local function Component(props)
386+
return createElement(className)
387+
end
388+
389+
local element = createElement(Component)
390+
391+
local result = shallow(element)
392+
393+
expect(result.instance).to.be.ok()
394+
expect(result.instance.ClassName).to.equal(className)
395+
end)
396+
397+
it("should not have an instance if it is a function component", function()
398+
local function Child()
399+
return createElement("Frame")
400+
end
401+
local function Component(props)
402+
return createElement(Child)
403+
end
404+
405+
local element = createElement(Component)
406+
407+
local result = shallow(element)
408+
409+
expect(result.instance).never.to.be.ok()
410+
end)
411+
end)
412+
382413
describe("find children", function()
383414
local function Component(props)
384415
return createElement("Frame", {}, props.children)
@@ -648,6 +679,47 @@ return function()
648679
end)
649680
end)
650681

682+
describe("hostKey constraint", function()
683+
it("should find the child element", function()
684+
local hostKey = "Child"
685+
local element = createElement(Component, {
686+
children = {
687+
[hostKey] = createElement("TextLabel"),
688+
},
689+
})
690+
691+
local result = shallow(element)
692+
693+
local constraints = {
694+
hostKey = hostKey,
695+
}
696+
local children = result:find(constraints)
697+
698+
expect(#children).to.equal(1)
699+
700+
local child = children[1]
701+
702+
expect(child.type.kind).to.equal(ElementKind.Host)
703+
end)
704+
705+
it("should return an empty list when no children is found", function()
706+
local element = createElement(Component, {
707+
children = {
708+
Child = createElement("TextLabel"),
709+
},
710+
})
711+
712+
local result = shallow(element)
713+
714+
local constraints = {
715+
hostKey = "NotFound",
716+
}
717+
local children = result:find(constraints)
718+
719+
expect(next(children)).never.to.be.ok()
720+
end)
721+
end)
722+
651723
it("should throw if the constraint does not exist", function()
652724
local element = createElement("Frame")
653725

@@ -734,4 +806,63 @@ return function()
734806
expect(#children).to.equal(1)
735807
end)
736808
end)
809+
810+
describe("findUnique", function()
811+
it("should return the only child when no constraints are given", function()
812+
local element = createElement("Frame", {}, {
813+
Child = createElement("TextLabel"),
814+
})
815+
816+
local result = shallow(element)
817+
818+
local child = result:findUnique()
819+
820+
expect(child.type.kind).to.equal(ElementKind.Host)
821+
expect(child.type.className).to.equal("TextLabel")
822+
end)
823+
824+
it("should return the only child that satifies the constraint", function()
825+
local element = createElement("Frame", {}, {
826+
ChildA = createElement("TextLabel"),
827+
ChildB = createElement("TextButton"),
828+
})
829+
830+
local result = shallow(element)
831+
832+
local child = result:findUnique({
833+
className = "TextLabel",
834+
})
835+
836+
expect(child.type.className).to.equal("TextLabel")
837+
end)
838+
839+
it("should throw if there is not any child element", function()
840+
local element = createElement("Frame")
841+
842+
local result = shallow(element)
843+
844+
local function shouldThrow()
845+
result:findUnique()
846+
end
847+
848+
expect(shouldThrow).to.throw()
849+
end)
850+
851+
it("should throw if more than one child satisfies the constraint", function()
852+
local element = createElement("Frame", {}, {
853+
ChildA = createElement("TextLabel"),
854+
ChildB = createElement("TextLabel"),
855+
})
856+
857+
local result = shallow(element)
858+
859+
local function shouldThrow()
860+
result:findUnique({
861+
className = "TextLabel",
862+
})
863+
end
864+
865+
expect(shouldThrow).to.throw()
866+
end)
867+
end)
737868
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
local Symbol = require(script.Parent.Parent.Parent.Symbol)
2+
3+
local AnonymousFunction = Symbol.named("AnonymousFunction")
4+
5+
return AnonymousFunction
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
local IndentedOutput = {}
2+
local IndentedOutputMetatable = {
3+
__index = IndentedOutput,
4+
}
5+
6+
function IndentedOutput.new(indentation)
7+
indentation = indentation or 2
8+
9+
local output = {
10+
_level = 0,
11+
_indentation = (" "):rep(indentation),
12+
_lines = {},
13+
}
14+
15+
setmetatable(output, IndentedOutputMetatable)
16+
17+
return output
18+
end
19+
20+
function IndentedOutput:write(line, ...)
21+
if select("#", ...) > 0 then
22+
line = line:format(...)
23+
end
24+
25+
local indentedLine = ("%s%s"):format(self._indentation:rep(self._level), line)
26+
27+
table.insert(self._lines, indentedLine)
28+
end
29+
30+
function IndentedOutput:push()
31+
self._level = self._level + 1
32+
end
33+
34+
function IndentedOutput:pop()
35+
self._level = math.max(self._level - 1, 0)
36+
end
37+
38+
function IndentedOutput:writeAndPush(line)
39+
self:write(line)
40+
self:push()
41+
end
42+
43+
function IndentedOutput:popAndWrite(line)
44+
self:pop()
45+
self:write(line)
46+
end
47+
48+
function IndentedOutput:join(separator)
49+
separator = separator or "\n"
50+
51+
return table.concat(self._lines, separator)
52+
end
53+
54+
return IndentedOutput

0 commit comments

Comments
 (0)