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

Commit

Permalink
Merge 7ef84d3 into dad8c75
Browse files Browse the repository at this point in the history
  • Loading branch information
jeparlefrancais committed Aug 9, 2019
2 parents dad8c75 + 7ef84d3 commit cb6019c
Show file tree
Hide file tree
Showing 44 changed files with 3,344 additions and 254 deletions.
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ When submitting a new feature, add tests for all functionality.

We use [LuaCov](https://keplerproject.github.io/luacov) for keeping track of code coverage. We'd like it to be as close to 100% as possible, but it's not always possible. Adding tests just for the purpose of getting coverage isn't useful; we should strive to make only useful tests!

### Snapshots
As part of the test suite, there are tests that generates snapshot files (located under `./RoactSnapshots`). To sync back the generated StringValues produce by these on the file system, we suggest using a tool like [`run-in-roblox`](https://github.com/LPGhatguy/run-in-roblox) to make the workflow as simple as possible. In the case where you only need to sync a few files, you can use the plugin to automatically copy back the generated string values from the run mode into module scripts when going back to edit mode. Then simply copy-paste into the new snapshots into the file-system.

## Release Checklist
When releasing a new version of Roact, do these things:

Expand Down
41 changes: 41 additions & 0 deletions SnapshotsPlugin/EditModeMain.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Settings = require(script.Parent.Settings)

local function SyncSnapshots(newSnapshots)
local snapshotsFolder = ReplicatedStorage:FindFirstChild(Settings.SnapshotFolderName)

if not snapshotsFolder then
snapshotsFolder = Instance.new("Folder")
snapshotsFolder.Name = Settings.SnapshotFolderName
snapshotsFolder.Parent = ReplicatedStorage
end

for name, value in pairs(newSnapshots) do
local snapshot = Instance.new("ModuleScript")
snapshot.Name = name
snapshot.Source = value
snapshot.Parent = snapshotsFolder
end
end

local function PluginEditMode(plugin)
local isPluginDeactivated = false

plugin.Deactivation:Connect(function()
isPluginDeactivated = true
end)

while not isPluginDeactivated do
local newSnapshots = plugin:GetSetting(Settings.PluginSettingName)

if newSnapshots then
SyncSnapshots(newSnapshots)
plugin:SetSetting(Settings.PluginSettingName, false)
end

wait(Settings.SyncDelay)
end
end

return PluginEditMode
25 changes: 25 additions & 0 deletions SnapshotsPlugin/RunModeMain.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Settings = require(script.Parent.Settings)

local function PluginRunMode(plugin)
plugin.Unloading:Connect(function()
local snapshotsFolder = ReplicatedStorage:FindFirstChild(Settings.SnapshotFolderName)

local newSnapshots = {}

if not snapshotsFolder then
return
end

for _, snapshot in pairs(snapshotsFolder:GetChildren()) do
if snapshot:IsA("StringValue") then
newSnapshots[snapshot.Name] = snapshot.Value
end
end

plugin:SetSetting(Settings.PluginSettingName, newSnapshots)
end)
end

return PluginRunMode
17 changes: 17 additions & 0 deletions SnapshotsPlugin/Settings.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local function IndexError(_, key)
local message = ("%q (%s) is not a valid member of Settings"):format(
tostring(key),
typeof(key)
)

error(message, 2)
end

return setmetatable({
SnapshotFolderName = "RoactSnapshots",
PluginSettingName = "NewRoactSnapshots",
SyncDelay = 1,
}, {
__index = IndexError,
__newindex = IndexError,
})
12 changes: 12 additions & 0 deletions SnapshotsPlugin/init.server.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
local RunService = game:GetService("RunService")

local EditModeMain = require(script.EditModeMain)
local RunModeMain = require(script.RunModeMain)

if RunService:IsEdit() then
EditModeMain(plugin)
else
if RunService:IsClient() then
RunModeMain(plugin)
end
end
154 changes: 153 additions & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ The result is a `RoactTree`, which is an opaque handle that represents a tree of

---

### Roact.shallow
```
Roact.shallow(element, [options]) -> ShallowWrapper
```
Options:
```lua
{
depth: number -- default to 1
}
```

Mounts the tree from the given element and wraps the root virtual node into a ShallowWrapper.
-- is this too specific/technical?

---

### Roact.update
```
Roact.update(tree, element) -> RoactTree
Expand Down Expand Up @@ -608,4 +624,140 @@ end
As with `setState`, you can set use the constant `Roact.None` to remove a field from the state.

!!! note
`getDerivedStateFromProps` is a *static* lifecycle method. It does not have access to `self`, and must be a pure function.
`getDerivedStateFromProps` is a *static* lifecycle method. It does not have access to `self`, and must be a pure function.

---

## ShallowWrapper

### Fields

#### type
```
type: {
kind: ElementKind
}
```

The type dictionary always has the `kind` field that tell the component type. Additionally, depending of the kind of component, other information can be included.

| kind | fields | description |
| --- | --- | --- |
| Host | className: string | the ClassName of the instance |
| Function | functionComponent: function | the function that renders the element |
| Stateful | component: table | the class-like table used to render the element |

---

#### props
The props of the ShallowWrapper.

!!! note
This dictionary will not contain the `Roact.Children` prop. To obtain the children elements wrapped into a ShallowWrapper, use the method `getChildren()`

---

#### hostKey
The `hostKey` that is used to map the element to it's parent.

---

### Methods

#### childrenCount
```
childrenCount() -> number
```
Returns the amount of children that the current ShallowWrapper contains.

---

#### find
```
find([constraints]) -> list[ShallowWrapper]
```
When a dictionary of constraints is provided, the function will filter the children that do not satisfy all given constraints. Otherwise, as `getChildren` do, it returns a list of all children wrapped into ShallowWrappers.

---

#### findUnique
```
findUnique([constraints]) -> ShallowWrapper
```
Similar to `find`, this method will assert that only one child satisfies the given constraints, or in the case where none is provided, will assert that there is simply only one child.

---

#### getChildren
```
getChildren() -> list[ShallowWrapper]
```
Returns a list of all children wrapped into ShallowWrappers.

---

#### getInstance
```
getInstance() -> Instance or nil
```
Returns the instance object associated with the ShallowWrapper. It can return `nil` if the component wrapped by the ShallowWrapper does not render an instance, but rather another component. Here is an example:

```lua
local function CoolComponent(props)
return Roact.createElement("TextLabel")
end

local function MainComponent(props)
return Roact.createElement("Frame", {}, {
Cool = Roact.createElement(CoolComponent),
})
end
```

If we shallow-render the `MainComponent`, the default depth will not render the CoolComponent.

```lua
local element = Roact.createElement(MainComponent)

local tree = Roact.mount(element)

local wrapper = tree:getShallowWrapper()

print(wrapper:getInstance() == nil) -- prints false

local coolWrapper = wrapper:findUnique()

print(coolWrapper:getInstance() == nil) -- prints true
```

---

#### matchSnapshot
```
matchSnapshot(identifier)
```
If no previous snapshot with the given identifier exists, it will create a new StringValue instance that will contain Lua code representing the current ShallowWrapper. When an existing snapshot is found (a ModuleScript named as the provided identifier), it will require the ModuleScript and load the data from it. Then, if the loaded data is different from the current ShallowWrapper, an error will be thrown.

!!! note
As mentionned, `matchSnapshot` will create a StringValue, named like the given identifier, in which the generated lua code will be assigned to the Value property. When these values are generated in Studio during run mode, it's important to copy back the values and convert them into ModuleScripts.

---

#### snapshotToString
```
snapshotToString() -> string
```
Returns the string source of the snapshot. Useful for debugging purposes.

---

##### Constraints
Constraints are passed through a dictionary that maps a constraint name to it's value.

| name | value type | description |
| --- | --- | --- |
| kind | ElementKind | Filters with the ElementKind of the rendered elements |
| className | string | Filters children that renders to host instance of the given class name |
| component | string, function or table | Filter children from their components, by finding the functional component original function, the sub-class table of Roact.Component or from the class name of the instance rendered |
| props | dictionary | Filters elements that contains at least the given set of props |
| hostKey | string | Filters elements by the host key used |
6 changes: 6 additions & 0 deletions snapshots-plugin.project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "Snapshots Plugin",
"tree": {
"$path": "SnapshotsPlugin"
}
}
25 changes: 17 additions & 8 deletions src/Component.spec/setState.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ return function()
local createSpy = require(script.Parent.Parent.createSpy)
local None = require(script.Parent.Parent.None)
local NoopRenderer = require(script.Parent.Parent.NoopRenderer)
local VirtualTree = require(script.Parent.Parent.VirtualTree)

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

local noopReconciler = createReconciler(NoopRenderer)

local function mountWithNoop(element, hostParent, hostKey)
return VirtualTree.mount(element, {
hostParent = hostParent,
hostKey = hostKey,
reconciler = noopReconciler
})
end

describe("setState", function()
it("should not trigger an extra update when called in init", function()
local renderCount = 0
Expand All @@ -35,7 +44,7 @@ return function()

local initElement = createElement(InitComponent)

noopReconciler.mountVirtualTree(initElement)
mountWithNoop(initElement)

expect(renderCount).to.equal(1)
expect(updateCount).to.equal(0)
Expand All @@ -53,7 +62,7 @@ return function()

local renderElement = createElement(TestComponent)

local success, result = pcall(noopReconciler.mountVirtualTree, renderElement)
local success, result = pcall(mountWithNoop, renderElement)

expect(success).to.equal(false)
expect(result:match("render")).to.be.ok()
Expand All @@ -76,9 +85,9 @@ return function()
local initialElement = createElement(TestComponent)
local updatedElement = createElement(TestComponent)

local tree = noopReconciler.mountVirtualTree(initialElement)
local tree = mountWithNoop(initialElement)

local success, result = pcall(noopReconciler.updateVirtualTree, tree, updatedElement)
local success, result = pcall(VirtualTree.update, tree, updatedElement)

expect(success).to.equal(false)
expect(result:match("shouldUpdate")).to.be.ok()
Expand All @@ -100,9 +109,9 @@ return function()

local initialElement = createElement(TestComponent)
local updatedElement = createElement(TestComponent)
local tree = noopReconciler.mountVirtualTree(initialElement)
local tree = mountWithNoop(initialElement)

local success, result = pcall(noopReconciler.updateVirtualTree, tree, updatedElement)
local success, result = pcall(VirtualTree.update, tree, updatedElement)

expect(success).to.equal(false)
expect(result:match("willUpdate")).to.be.ok()
Expand All @@ -123,9 +132,9 @@ return function()
end

local element = createElement(TestComponent)
local tree = noopReconciler.mountVirtualTree(element)
local tree = mountWithNoop(element)

local success, result = pcall(noopReconciler.unmountVirtualTree, tree)
local success, result = pcall(VirtualTree.unmount, tree)

expect(success).to.equal(false)
expect(result:match("willUnmount")).to.be.ok()
Expand Down
Loading

0 comments on commit cb6019c

Please sign in to comment.