add gs
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(user)
|
||||
return {
|
||||
user = user
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(users)
|
||||
return {
|
||||
users = users
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId)
|
||||
return {
|
||||
userId = userId
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, response)
|
||||
return {
|
||||
userId = userId,
|
||||
response = response
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId)
|
||||
return {
|
||||
userId = userId
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(placesInfos)
|
||||
return {
|
||||
placesInfos = placesInfos,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId)
|
||||
return {
|
||||
userId = userId,
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(deviceOrientation)
|
||||
return {
|
||||
deviceOrientation = deviceOrientation,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(count)
|
||||
return {
|
||||
count = count,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,26 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
--[[
|
||||
Passes a table that looks like this : { "universeId" : {json}, ... }
|
||||
|
||||
{
|
||||
"26034470" : {
|
||||
universeId : "26034470",
|
||||
placeId : "70542190",
|
||||
url : https://t5.rbxcdn.com/ed422c6fbb22280971cfb289f40ac814,
|
||||
final : true
|
||||
}, {...}, ...
|
||||
}
|
||||
|
||||
]]
|
||||
|
||||
--TODO MOBLUAPP-778 Refactor improper Setter Actions.
|
||||
return Action(script.Name, function(thumbnailsTable)
|
||||
assert(type(thumbnailsTable) == "table",
|
||||
string.format("SetGameThumbnails action expects thumbnailsTable to be a table, was %s", type(thumbnailsTable)))
|
||||
|
||||
return {
|
||||
thumbnails = thumbnailsTable
|
||||
}
|
||||
end)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, universeId)
|
||||
return {
|
||||
userId = userId,
|
||||
universeId = universeId,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, isFriend)
|
||||
return {
|
||||
userId = userId,
|
||||
isFriend = isFriend,
|
||||
}
|
||||
end)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, membershipType)
|
||||
return {
|
||||
userId = userId,
|
||||
membershipType = membershipType,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, presence, lastLocation)
|
||||
return {
|
||||
userId = tostring(userId),
|
||||
presence = presence,
|
||||
lastLocation = lastLocation,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,13 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, image, thumbnailType, thumbnailSize)
|
||||
return {
|
||||
userId = userId,
|
||||
image = image,
|
||||
thumbnailType = thumbnailType,
|
||||
thumbnailSize = thumbnailSize,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,51 @@
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local RunService = game:GetService('RunService')
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local BAR_SLICE_CENTER = Rect.new(1, 0, 2, 3)
|
||||
local BAR_MAX_SIZE = 15
|
||||
local BAR_MAX_AMPLITUDE = 40
|
||||
local BAR_DIAMETER = 4
|
||||
local BAR_PERIOD = 1.25
|
||||
|
||||
local LoadingBar = Roact.Component:extend("LoadingBar")
|
||||
|
||||
function LoadingBar:init()
|
||||
self.barRef = Roact.createRef()
|
||||
end
|
||||
|
||||
function LoadingBar:render()
|
||||
local zIndex = self.props.ZIndex
|
||||
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Image = "rbxasset://textures/ui/LuaApp/9-slice/gr-loading-indicator.png",
|
||||
ScaleType = "Slice",
|
||||
SliceCenter = BAR_SLICE_CENTER,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
ZIndex = zIndex,
|
||||
[Roact.Ref] = self.barRef,
|
||||
})
|
||||
end
|
||||
|
||||
function LoadingBar:didMount()
|
||||
self.connection = RunService.RenderStepped:Connect(function()
|
||||
local t = Workspace.DistributedGameTime
|
||||
local instance = self.barRef.current
|
||||
local period = 2.0 * math.pi / BAR_PERIOD
|
||||
|
||||
local width = (BAR_MAX_SIZE/2) * (1 - math.cos(2*t*period))
|
||||
instance.Size = UDim2.new(0, BAR_DIAMETER + width, 0, BAR_DIAMETER)
|
||||
|
||||
local x = BAR_MAX_AMPLITUDE * math.cos(t*period)
|
||||
instance.Position = UDim2.new(0.5, x - width/2 - BAR_DIAMETER/2, 0.5, 0)
|
||||
end)
|
||||
end
|
||||
|
||||
function LoadingBar:willUnmount()
|
||||
self.connection:Disconnect()
|
||||
end
|
||||
|
||||
return LoadingBar
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
return function()
|
||||
local LoadingBar = require(script.Parent.LoadingBar)
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(LoadingBar, {
|
||||
Position = UDim2.new(0.5, 0, 0.5, 5),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
local RetrievalStatus = {}
|
||||
|
||||
local EnumValues =
|
||||
{
|
||||
NotStarted = "NotStarted",
|
||||
Fetching = "Fetching",
|
||||
Done = "Done",
|
||||
Failed = "Failed",
|
||||
}
|
||||
|
||||
setmetatable(RetrievalStatus,
|
||||
{
|
||||
__newindex = function(t, key, index)
|
||||
end,
|
||||
__index = function(t, index)
|
||||
assert(EnumValues[index] ~= nil, ("RetrievalStatus Enum has no value: " .. tostring(index)))
|
||||
return EnumValues[index]
|
||||
end
|
||||
})
|
||||
|
||||
return RetrievalStatus
|
||||
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
|
||||
return {
|
||||
[0] = User.PresenceType.OFFLINE,
|
||||
[1] = User.PresenceType.ONLINE,
|
||||
[2] = User.PresenceType.IN_GAME,
|
||||
[3] = User.PresenceType.IN_STUDIO,
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local ThrottleUserId = require(CorePackages.AppTempCommon.LuaApp.Utils.ThrottleUserId)
|
||||
|
||||
local FIntLuaHomePageEnablePlacesListV1 = settings():GetFVariable("LuaHomePageEnablePlacesListV1V361")
|
||||
|
||||
-- Don't call this function globally because we cannot get the userId
|
||||
-- Reason: The LocalPlayer wouldn't be ready if we called it globally.
|
||||
return function()
|
||||
local throttleNumber = tonumber(FIntLuaHomePageEnablePlacesListV1)
|
||||
local userId = Players.LocalPlayer.UserId
|
||||
|
||||
return ThrottleUserId(throttleNumber, userId)
|
||||
end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local FFlagPlacesListV1 = require(CorePackages.AppTempCommon.LuaApp.Flags.IsPlacesListV1Enabled)
|
||||
|
||||
local FFlagLuaHomePeopleListV1V361 = settings():GetFFlag("LuaHomePeopleListV1V361")
|
||||
|
||||
return function()
|
||||
-- When we should fetch games data could change in the future
|
||||
-- Right now, we should fetch games data when people list on or place list on
|
||||
return FFlagLuaHomePeopleListV1V361 or FFlagPlacesListV1()
|
||||
end
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, conversationId, messageText)
|
||||
local payload = HttpService:JSONEncode({
|
||||
conversationId = conversationId,
|
||||
message = messageText,
|
||||
})
|
||||
|
||||
local url = string.format("%s/send-message", Url.CHAT_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, userId, clientId)
|
||||
local payload = HttpService:JSONEncode({
|
||||
participantuserId = userId
|
||||
})
|
||||
|
||||
local url = string.format("%s/start-one-to-one-conversation", Url.CHAT_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
This endpoint returns a promise that resolves to:
|
||||
[
|
||||
{
|
||||
"final": true,
|
||||
"url": "string",
|
||||
"retryToken": "string",
|
||||
"universeId": 0,
|
||||
"placeId": 0
|
||||
}, {...}, ...
|
||||
]
|
||||
]]
|
||||
|
||||
-- requestImpl - (function<promise<HttpResponse>>(url, requestMethod, options))
|
||||
-- imageTokens - (array<long>) the placeIds of the places you want to get thumbnails for
|
||||
-- height - (int) the height of the asset to render
|
||||
-- width - (int) the width of the asset to render
|
||||
return function(requestImpl, imageTokens, height, width)
|
||||
local args = {}
|
||||
|
||||
if height then
|
||||
table.insert(args, string.format("height=%d", height))
|
||||
end
|
||||
|
||||
if width then
|
||||
table.insert(args, string.format("width=%d", width))
|
||||
end
|
||||
|
||||
-- append all of the thumbnail tokens
|
||||
local totalTokens = 0
|
||||
for _, value in pairs(imageTokens) do
|
||||
totalTokens = totalTokens + 1
|
||||
table.insert(args, string.format("imageTokens=%s", value))
|
||||
end
|
||||
if totalTokens == 0 then
|
||||
error("cannot fetch thumbnails without tokens")
|
||||
end
|
||||
|
||||
-- construct the url
|
||||
local url = string.format("%sv1/games/game-thumbnails?%s", Url.GAME_URL, table.concat(args, "&"))
|
||||
|
||||
-- return a promise of the result listed above
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
This endpoint returns games' information with a batches of place ids
|
||||
Doc: https://games.roblox.com/docs#!/Games/get_v1_games_multiget_place_details
|
||||
{
|
||||
"placeId": 0,
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"url": "string",
|
||||
"builder": "string",
|
||||
"builderId": 0,
|
||||
"isPlayable": true,
|
||||
"reasonProhibited": "string",
|
||||
"universeId": 0,
|
||||
"universeRootPlaceId": 0,
|
||||
"price": 0,
|
||||
"imageToken": "string"
|
||||
}
|
||||
]]--
|
||||
|
||||
return function(requestImpl, placeIds)
|
||||
local argTable = {
|
||||
placeIds = placeIds,
|
||||
}
|
||||
|
||||
local args = Url:makeQueryString(argTable)
|
||||
local url = string.format("%s/v1/games/multiget-place-details?%s", Url.GAME_URL, args)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, placeIds)
|
||||
local argTable = {
|
||||
placeIds = placeIds,
|
||||
}
|
||||
|
||||
-- construct the url
|
||||
local args = Url:makeQueryString(argTable)
|
||||
local url = string.format("%s/v1/games/multiget-place-details?%s",
|
||||
Url.GAME_URL, args
|
||||
)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
This endpoint returns a promise that resolves to:
|
||||
|
||||
[
|
||||
{
|
||||
"success:" true,
|
||||
"count": "0"
|
||||
},
|
||||
]
|
||||
]]--
|
||||
|
||||
-- requestImpl - (function<promise<HttpResponse>>(url, requestMethod, options))
|
||||
return function(requestImpl)
|
||||
|
||||
local argTable = {
|
||||
userId = Players.LocalPlayer.UserId,
|
||||
}
|
||||
|
||||
local args = Url:makeQueryString(argTable)
|
||||
local url = string.format("%s/user/get-friendship-count?%s",
|
||||
Url.API_URL, tostring(Players.LocalPlayer.UserId), args
|
||||
)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, userId)
|
||||
local url = string.format("%s/users/%s/friends",
|
||||
Url.FRIEND_URL, userId
|
||||
)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
-- Endpoint documented here:
|
||||
-- https://presence.roblox.com/docs
|
||||
|
||||
return function(requestImpl, userIds)
|
||||
local userIdsToNumber = {}
|
||||
for _, id in pairs(userIds) do
|
||||
local idToNumber = tonumber(id)
|
||||
if idToNumber then
|
||||
table.insert(userIdsToNumber, idToNumber)
|
||||
end
|
||||
end
|
||||
|
||||
local payload = HttpService:JSONEncode({
|
||||
userIds = userIdsToNumber,
|
||||
})
|
||||
|
||||
local url = string.format("%s/presence/users", Url.PRESENCE_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
|
||||
local THUMBNAIL_TYPE_BY_NAME = {
|
||||
AvatarThumbnail = Enum.ThumbnailType.AvatarThumbnail,
|
||||
HeadShot = Enum.ThumbnailType.HeadShot,
|
||||
}
|
||||
|
||||
local THUMBNAIL_SIZE_BY_NAME = {
|
||||
Size48x48 = Enum.ThumbnailSize.Size48x48,
|
||||
Size60x60 = Enum.ThumbnailSize.Size60x60,
|
||||
Size100x100 = Enum.ThumbnailSize.Size100x100,
|
||||
Size150x150 = Enum.ThumbnailSize.Size150x150,
|
||||
Size352x352 = Enum.ThumbnailSize.Size352x352
|
||||
}
|
||||
|
||||
return function(userId, thumbnailType, thumbnailSize)
|
||||
return Promise.new(function(resolve, reject)
|
||||
--Async methods will yield the thread
|
||||
spawn(function()
|
||||
local result = {success = false}
|
||||
local success, message = pcall(function()
|
||||
local image, isFinal = Players:GetUserThumbnailAsync(
|
||||
userId, THUMBNAIL_TYPE_BY_NAME[thumbnailType], THUMBNAIL_SIZE_BY_NAME[thumbnailSize]
|
||||
)
|
||||
|
||||
result = {
|
||||
success = true,
|
||||
id = userId,
|
||||
thumbnailType = thumbnailType,
|
||||
thumbnailSize = thumbnailSize,
|
||||
|
||||
image = isFinal and image or nil,
|
||||
isFinal = isFinal,
|
||||
}
|
||||
end)
|
||||
|
||||
if success then
|
||||
resolve(result)
|
||||
else
|
||||
result.message = message
|
||||
reject(result)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,114 @@
|
||||
--[[
|
||||
Url Constructor
|
||||
|
||||
Provides a single location for base urls.
|
||||
|
||||
]]--
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
|
||||
-- helper functions
|
||||
local function parseBaseUrlInformation()
|
||||
-- get the current base url from the current configuration
|
||||
local baseUrl = ContentProvider.BaseUrl
|
||||
|
||||
-- keep a copy of the base url (https://www.roblox.com/)
|
||||
-- append a trailing slash if there isn't one
|
||||
if baseUrl:sub(#baseUrl) ~= "/" then
|
||||
baseUrl = baseUrl .. "/"
|
||||
end
|
||||
|
||||
-- parse out scheme (http, https)
|
||||
local _, schemeEnd = baseUrl:find("://")
|
||||
|
||||
-- parse out the prefix (www, kyle, ying, etc.)
|
||||
local prefixIndex, prefixEnd = baseUrl:find("%.", schemeEnd + 1)
|
||||
local basePrefix = baseUrl:sub(schemeEnd + 1, prefixIndex - 1)
|
||||
|
||||
-- parse out the domain (roblox.com/, sitetest1.robloxlabs.com/, etc.)
|
||||
local baseDomain = baseUrl:sub(prefixEnd + 1)
|
||||
|
||||
return baseUrl, basePrefix, baseDomain
|
||||
end
|
||||
local function preventTableModification(aTable, key, value)
|
||||
error("Attempt to modify read-only table")
|
||||
end
|
||||
local function createReadOnlyTable(aTable)
|
||||
return setmetatable({}, {
|
||||
__index = aTable,
|
||||
__newindex = preventTableModification,
|
||||
__metatable = false
|
||||
});
|
||||
end
|
||||
|
||||
|
||||
-- url construction building blocks
|
||||
local _baseUrl, _basePrefix, _baseDomain = parseBaseUrlInformation()
|
||||
|
||||
-- construct urls once
|
||||
local _baseApiUrl = string.format("https://api.%s", _baseDomain)
|
||||
local _baseAuthUrl = string.format("https://auth.%s", _baseDomain)
|
||||
local _baseChatUrl = string.format("https://chat.%sv2", _baseDomain)
|
||||
local _baseFriendUrl = string.format("https://friends.%sv1", _baseDomain)
|
||||
local _baseGameAssetUrl = string.format("https://assetgame.%s", _baseDomain)
|
||||
local _baseGamesUrl = string.format("https://games.%s", _baseDomain)
|
||||
local _baseNotificationUrl = string.format("https://notifications.%s", _baseDomain)
|
||||
local _basePresenceUrl = string.format("https://presence.%sv1", _baseDomain)
|
||||
local _baseRealtimeUrl = string.format("https://realtime.%s", _baseDomain)
|
||||
local _baseWebUrl = string.format("https://web.%s", _baseDomain)
|
||||
local _baseWwwUrl = string.format("https://www.%s", _baseDomain)
|
||||
local _baseAdsUrl = string.format("https://ads.%s", _baseDomain)
|
||||
|
||||
-- public api
|
||||
local Url = {
|
||||
DOMAIN = _baseDomain,
|
||||
PREFIX = _basePrefix,
|
||||
BASE_URL = _baseUrl,
|
||||
API_URL = _baseApiUrl,
|
||||
AUTH_URL = _baseAuthUrl,
|
||||
GAME_URL = _baseGamesUrl,
|
||||
GAME_ASSET_URL = _baseGameAssetUrl,
|
||||
CHAT_URL = _baseChatUrl,
|
||||
FRIEND_URL = _baseFriendUrl,
|
||||
PRESENCE_URL = _basePresenceUrl,
|
||||
NOTIFICATION_URL = _baseNotificationUrl,
|
||||
REALTIME_URL = _baseRealtimeUrl,
|
||||
WEB_URL = _baseWebUrl,
|
||||
WWW_URL = _baseWwwUrl,
|
||||
ADS_URL = _baseAdsUrl,
|
||||
}
|
||||
|
||||
function Url:getUserProfileUrl(userId)
|
||||
return string.format("%susers/%s/profile", self.BASE_URL, userId)
|
||||
end
|
||||
|
||||
function Url:isVanitySite()
|
||||
return self.PREFIX ~= "www"
|
||||
end
|
||||
|
||||
-- data - (table<string, string>) a table of key/value pairs to format
|
||||
function Url:makeQueryString(data)
|
||||
--NOTE - This function can be used to create a query string of parameters
|
||||
-- at the end of url query, or create a application/form-url-encoded post body string
|
||||
local params = {}
|
||||
|
||||
-- NOTE - Arrays are handled, but generally data is expected to be flat.
|
||||
for key, value in pairs(data) do
|
||||
if value ~= nil then --for optional params
|
||||
if type(value) == "table" then
|
||||
for i = 1, #value do
|
||||
table.insert(params, key .. "=" .. value[i])
|
||||
end
|
||||
else
|
||||
table.insert(params, key .. "=" .. tostring(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(params, "&")
|
||||
end
|
||||
|
||||
|
||||
-- prevent anyone from modifying this table:
|
||||
Url = createReadOnlyTable(Url)
|
||||
|
||||
return Url
|
||||
@@ -0,0 +1,12 @@
|
||||
return function()
|
||||
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local Url = require(script.Parent.Url)
|
||||
|
||||
it("The base url has not been changed for debugging", function()
|
||||
local baseUrl = ContentProvider.BaseUrl
|
||||
|
||||
expect(baseUrl).to.equal(Url.BASE_URL)
|
||||
end)
|
||||
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
--[[
|
||||
A function to return a fake ID, used for testing
|
||||
]]
|
||||
|
||||
local lastId = 0
|
||||
|
||||
return function()
|
||||
lastId = lastId + 1
|
||||
return ("MOCK-%d"):format(lastId)
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
local ThumbnailRequest = {}
|
||||
|
||||
function ThumbnailRequest.new()
|
||||
local self = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ThumbnailRequest.fromData(thumbnailType, thumbnailSize)
|
||||
local self = ThumbnailRequest.new()
|
||||
|
||||
self.thumbnailType = thumbnailType
|
||||
self.thumbnailSize = thumbnailSize
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return ThumbnailRequest
|
||||
@@ -0,0 +1,117 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local MockId = require(CorePackages.AppTempCommon.LuaApp.MockId)
|
||||
|
||||
local FFlagFixUsersReducerDataLoss = settings():GetFFlag("FixUsersReducerDataLoss")
|
||||
|
||||
local User = {}
|
||||
|
||||
User.PresenceType = {
|
||||
OFFLINE = "OFFLINE",
|
||||
ONLINE = "ONLINE",
|
||||
IN_GAME = "IN_GAME",
|
||||
IN_STUDIO = "IN_STUDIO",
|
||||
}
|
||||
|
||||
function User.new()
|
||||
local self = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function User.mock()
|
||||
local self = User.new()
|
||||
|
||||
self.id = MockId()
|
||||
|
||||
self.isFetching = false
|
||||
self.isFriend = false
|
||||
self.lastLocation = nil
|
||||
self.name = "USER NAME"
|
||||
self.universeId = nil
|
||||
self.placeId = nil
|
||||
self.rootPlaceId = nil
|
||||
self.gameInstanceId = nil
|
||||
self.lastOnline = 0
|
||||
self.presence = User.PresenceType.OFFLINE
|
||||
self.membership = nil
|
||||
if not FFlagFixUsersReducerDataLoss then
|
||||
self.thumbnails = {}
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function User.fromData(id, name, isFriend)
|
||||
local self = User.new()
|
||||
|
||||
self.id = tostring(id)
|
||||
|
||||
self.isFetching = false
|
||||
self.isFriend = isFriend
|
||||
self.lastLocation = nil
|
||||
self.name = name
|
||||
self.universeId = nil
|
||||
self.placeId = nil
|
||||
self.rootPlaceId = nil
|
||||
self.gameInstanceId = nil
|
||||
self.lastOnline = 0
|
||||
if FFlagFixUsersReducerDataLoss then
|
||||
self.presence = (self.id == tostring(Players.LocalPlayer.UserId)) and User.PresenceType.ONLINE or nil
|
||||
else
|
||||
self.presence = (self.id == tostring(Players.LocalPlayer.UserId)) and User.PresenceType.ONLINE or
|
||||
User.PresenceType.OFFLINE
|
||||
self.thumbnails = {}
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function User.compare(user1, user2)
|
||||
assert(not(user1 == nil and user2 == nil))
|
||||
assert(user1 == nil or typeof(user1) == "table")
|
||||
assert(user2 == nil or typeof(user2) == "table")
|
||||
|
||||
-- Return false if any of the provided input is nil(empty).
|
||||
if not user1 or not user2 then
|
||||
return false
|
||||
end
|
||||
|
||||
for field, valueInUser2 in pairs(user2) do
|
||||
if user1[field] ~= valueInUser2 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for field, valueInUser1 in pairs(user1) do
|
||||
if user2[field] ~= valueInUser1 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function User.userPresenceToText(localization, user)
|
||||
local presence = user.presence
|
||||
local lastLocation = user.lastLocation
|
||||
|
||||
if not presence then
|
||||
return ''
|
||||
end
|
||||
|
||||
if presence == User.PresenceType.OFFLINE then
|
||||
return localization:Format("Common.Presence.Label.Offline")
|
||||
elseif presence == User.PresenceType.ONLINE then
|
||||
return localization:Format("Common.Presence.Label.Online")
|
||||
elseif (presence == User.PresenceType.IN_GAME) or (presence == User.PresenceType.IN_STUDIO) then
|
||||
if lastLocation ~= nil then
|
||||
return lastLocation
|
||||
else
|
||||
return localization:Format("Common.Presence.Label.Online")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return User
|
||||
@@ -0,0 +1,68 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
|
||||
it("should detect if provided users are identical", function()
|
||||
local clone1 = User.fromData(1, "Andy", true)
|
||||
local clone2 = Immutable.Set(clone1, "isFriend", true)
|
||||
|
||||
local result = User.compare(clone1, clone2)
|
||||
expect(result).to.equal(true)
|
||||
|
||||
result = User.compare(clone2, clone1)
|
||||
expect(result).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should detect when there is one or more fields with different values", function()
|
||||
local andy = User.fromData(1, "Andy", true)
|
||||
local ollie = Immutable.Set(andy, "name", "Ollie")
|
||||
|
||||
local result = User.compare(andy, ollie)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(ollie, andy)
|
||||
expect(result).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should detect descrepancy when one user model contains more fields than the other", function()
|
||||
local andy = User.fromData(1, "Andy", true)
|
||||
local secretlyNotAndy = Immutable.Set(andy, "someDifferentField", "I'm Ollie!")
|
||||
|
||||
local result = User.compare(andy, secretlyNotAndy)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(secretlyNotAndy, andy)
|
||||
expect(result).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should throw if invalid input is provided", function()
|
||||
local aString = "I'm not a table."
|
||||
local teddy = User.fromData(1, "Teddy", true)
|
||||
|
||||
expect(function() User.compare(nil, nil) end).to.throw()
|
||||
expect(function() User.compare(aString, nil) end).to.throw()
|
||||
expect(function() User.compare(nil, aString) end).to.throw()
|
||||
expect(function() User.compare(aString, aString) end).to.throw()
|
||||
expect(function() User.compare(teddy, aString) end).to.throw()
|
||||
expect(function() User.compare(aString, teddy) end).to.throw()
|
||||
end)
|
||||
|
||||
it("should return false if any one of the input is empty or nil)", function()
|
||||
local emptyTable = {}
|
||||
local teddy = User.fromData(1, "Teddy", true)
|
||||
|
||||
local result = User.compare(teddy, nil)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(nil, teddy)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(teddy, emptyTable)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(emptyTable, teddy)
|
||||
expect(result).to.equal(false)
|
||||
end)
|
||||
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
local percentReporting = tonumber(settings():GetFVariable("PercentReportingNetworkProfileAfterStartup"))
|
||||
|
||||
local FEATURE_NAME = "NetworkProfileDuringStartup"
|
||||
local QUEUED_MEASURE_NAME = "Queued"
|
||||
local NAME_LOOKUP_MEASURE_NAME = "NameLookup"
|
||||
local CONNECT_MEASURE_NAME = "Connect"
|
||||
local SSL_HANDSHAKE_MEASURE_NAME = "SSLHandshake"
|
||||
local MAKE_REQUEST_MEASURE_NAME = "MakeRequest"
|
||||
local RECEIVE_RESPONSE_MEASURE_NAME = "ReceiveResponse"
|
||||
|
||||
local NetworkProfiler = {}
|
||||
NetworkProfiler.__index = NetworkProfiler
|
||||
|
||||
NetworkProfiler.aggregate = {
|
||||
queued = 0.0,
|
||||
nameLookup = 0.0,
|
||||
connect = 0.0,
|
||||
sslHandshake = 0.0,
|
||||
makeRequest = 0.0,
|
||||
receiveResponse = 0.0,
|
||||
}
|
||||
|
||||
function NetworkProfiler:track(timeProfile)
|
||||
self.aggregate.queued = self.aggregate.queued + timeProfile.queued
|
||||
if timeProfile.nameLookup >= 0 then
|
||||
self.aggregate.nameLookup = self.aggregate.nameLookup + timeProfile.nameLookup
|
||||
end
|
||||
if timeProfile.connect >= 0 then
|
||||
self.aggregate.connect = self.aggregate.connect + timeProfile.connect
|
||||
end
|
||||
if timeProfile.sslHandshake >= 0 then
|
||||
self.aggregate.sslHandshake = self.aggregate.sslHandshake + timeProfile.sslHandshake
|
||||
end
|
||||
if timeProfile.makeRequest >= 0 then
|
||||
self.aggregate.makeRequest = self.aggregate.makeRequest + timeProfile.makeRequest
|
||||
end
|
||||
if timeProfile.receiveResponse >= 0 then
|
||||
self.aggregate.receiveResponse = self.aggregate.receiveResponse + timeProfile.receiveResponse
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkProfiler:report(reportToDiag)
|
||||
reportToDiag(FEATURE_NAME, QUEUED_MEASURE_NAME, self.aggregate.queued, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, NAME_LOOKUP_MEASURE_NAME, self.aggregate.nameLookup, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, CONNECT_MEASURE_NAME, self.aggregate.connect, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, SSL_HANDSHAKE_MEASURE_NAME, self.aggregate.sslHandshake, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, MAKE_REQUEST_MEASURE_NAME, self.aggregate.makeRequest, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, RECEIVE_RESPONSE_MEASURE_NAME, self.aggregate.receiveResponse, percentReporting)
|
||||
end
|
||||
|
||||
return NetworkProfiler
|
||||
@@ -0,0 +1,343 @@
|
||||
--[[
|
||||
An implementation of Promises similar to Promise/A+.
|
||||
]]
|
||||
local TU = require(script.Parent.TableUtilities)
|
||||
|
||||
local PROMISE_DEBUG = true
|
||||
|
||||
-- If promise debugging is on, use a version of pcall that warns on failure.
|
||||
-- This is useful for finding errors that happen within Promise itself.
|
||||
local wpcall
|
||||
if PROMISE_DEBUG then
|
||||
wpcall = function(f, ...)
|
||||
local result = { pcall(f, ...) }
|
||||
|
||||
if not result[1] then
|
||||
warn(result[2])
|
||||
end
|
||||
|
||||
return unpack(result)
|
||||
end
|
||||
else
|
||||
wpcall = pcall
|
||||
end
|
||||
|
||||
--[[
|
||||
Creates a function that invokes a callback with correct error handling and
|
||||
resolution mechanisms.
|
||||
]]
|
||||
local function createAdvancer(callback, resolve, reject)
|
||||
return function(...)
|
||||
local result = { wpcall(callback, ...) }
|
||||
local ok = table.remove(result, 1)
|
||||
|
||||
if ok then
|
||||
resolve(unpack(result))
|
||||
else
|
||||
reject(unpack(result))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function isEmpty(t)
|
||||
return next(t) == nil
|
||||
end
|
||||
|
||||
local Promise = {}
|
||||
Promise.__index = Promise
|
||||
|
||||
Promise.Status = {
|
||||
Started = "Started",
|
||||
Resolved = "Resolved",
|
||||
Rejected = "Rejected",
|
||||
}
|
||||
|
||||
--[[
|
||||
Constructs a new Promise with the given initializing callback.
|
||||
|
||||
This is generally only called when directly wrapping a non-promise API into
|
||||
a promise-based version.
|
||||
|
||||
The callback will receive 'resolve' and 'reject' methods, used to start
|
||||
invoking the promise chain.
|
||||
|
||||
For example:
|
||||
|
||||
local function get(url)
|
||||
return Promise.new(function(resolve, reject)
|
||||
spawn(function()
|
||||
resolve(HttpService:GetAsync(url))
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
get("https://google.com")
|
||||
:andThen(function(stuff)
|
||||
print("Got some stuff!", stuff)
|
||||
end)
|
||||
]]
|
||||
function Promise.new(callback)
|
||||
local promise = {
|
||||
-- Used to locate where a promise was created
|
||||
_source = debug.traceback(),
|
||||
|
||||
-- A tag to identify us as a promise
|
||||
_type = "Promise",
|
||||
|
||||
_status = Promise.Status.Started,
|
||||
|
||||
-- A table containing a list of all results, whether success or failure.
|
||||
-- Only valid if _status is set to something besides Started
|
||||
_value = nil,
|
||||
|
||||
-- Queues representing functions we should invoke when we update!
|
||||
_queuedResolve = {},
|
||||
_queuedReject = {},
|
||||
}
|
||||
|
||||
setmetatable(promise, Promise)
|
||||
|
||||
local function resolve(...)
|
||||
promise:_resolve(...)
|
||||
end
|
||||
|
||||
local function reject(...)
|
||||
promise:_reject(...)
|
||||
end
|
||||
|
||||
local ok, err = wpcall(callback, resolve, reject)
|
||||
|
||||
if not ok and promise._status == Promise.Status.Started then
|
||||
reject(err)
|
||||
end
|
||||
|
||||
return promise
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a promise that represents the immediately resolved value.
|
||||
]]
|
||||
function Promise.resolve(value)
|
||||
return Promise.new(function(resolve)
|
||||
resolve(value)
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a promise that represents the immediately rejected value.
|
||||
]]
|
||||
function Promise.reject(value)
|
||||
return Promise.new(function(_, reject)
|
||||
reject(value)
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns a new promise that:
|
||||
* is resolved when all input promises resolve
|
||||
* is rejected if ANY input promises reject
|
||||
]]
|
||||
function Promise.all(...)
|
||||
local promises = {...}
|
||||
|
||||
-- check if we've been given a list of promises, not just a variable number of promises
|
||||
if type(promises[1]) == "table" and promises[1]._type ~= "Promise" then
|
||||
-- we've been given a table of promises already
|
||||
promises = promises[1]
|
||||
end
|
||||
|
||||
return Promise.new(function(resolve, reject)
|
||||
local isResolved = false
|
||||
local results = {}
|
||||
local totalCompleted = 0
|
||||
local function promiseCompleted(index, result)
|
||||
if isResolved then
|
||||
return
|
||||
end
|
||||
|
||||
results[index] = result
|
||||
totalCompleted = totalCompleted + 1
|
||||
|
||||
if totalCompleted == #promises then
|
||||
resolve(results)
|
||||
isResolved = true
|
||||
end
|
||||
end
|
||||
|
||||
if #promises == 0 then
|
||||
resolve(results)
|
||||
isResolved = true
|
||||
return
|
||||
end
|
||||
|
||||
for index, promise in ipairs(promises) do
|
||||
-- if a promise isn't resolved yet, add listeners for when it does
|
||||
if promise._status == Promise.Status.Started then
|
||||
promise:andThen(function(result)
|
||||
promiseCompleted(index, result)
|
||||
end):catch(function(reason)
|
||||
isResolved = true
|
||||
reject(reason)
|
||||
end)
|
||||
|
||||
-- if a promise is already resolved, move on
|
||||
elseif promise._status == Promise.Status.Resolved then
|
||||
promiseCompleted(index, unpack(promise._value))
|
||||
|
||||
-- if a promise is rejected, reject the whole chain
|
||||
else --if promise._status == Promise.Status.Rejected then
|
||||
isResolved = true
|
||||
reject(unpack(promise._value))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Is the given object a Promise instance?
|
||||
]]
|
||||
function Promise.is(object)
|
||||
if type(object) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
return object._type == "Promise"
|
||||
end
|
||||
|
||||
--[[
|
||||
Creates a new promise that receives the result of this promise.
|
||||
|
||||
The given callbacks are invoked depending on that result.
|
||||
]]
|
||||
function Promise:andThen(successHandler, failureHandler)
|
||||
-- Create a new promise to follow this part of the chain
|
||||
return Promise.new(function(resolve, reject)
|
||||
-- Our default callbacks just pass values onto the next promise.
|
||||
-- This lets success and failure cascade correctly!
|
||||
|
||||
local successCallback = resolve
|
||||
if successHandler then
|
||||
successCallback = createAdvancer(successHandler, resolve, reject)
|
||||
end
|
||||
|
||||
local failureCallback = reject
|
||||
if failureHandler then
|
||||
failureCallback = createAdvancer(failureHandler, resolve, reject)
|
||||
end
|
||||
|
||||
if self._status == Promise.Status.Started then
|
||||
-- If we haven't resolved yet, put ourselves into the queue
|
||||
table.insert(self._queuedResolve, successCallback)
|
||||
table.insert(self._queuedReject, failureCallback)
|
||||
elseif self._status == Promise.Status.Resolved then
|
||||
-- This promise has already resolved! Trigger success immediately.
|
||||
successCallback(unpack(self._value))
|
||||
elseif self._status == Promise.Status.Rejected then
|
||||
-- This promise died a terrible death! Trigger failure immediately.
|
||||
failureCallback(unpack(self._value))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Used to catch any errors that may have occurred in the promise.
|
||||
]]
|
||||
function Promise:catch(failureCallback)
|
||||
return self:andThen(nil, failureCallback)
|
||||
end
|
||||
|
||||
--[[
|
||||
Yield until the promise is completed.
|
||||
|
||||
This matches the execution model of normal Roblox functions.
|
||||
]]
|
||||
function Promise:await()
|
||||
if self._status == Promise.Status.Started then
|
||||
local result
|
||||
local bindable = Instance.new("BindableEvent")
|
||||
|
||||
self:andThen(function(...)
|
||||
result = {...}
|
||||
bindable:Fire(true)
|
||||
end, function(...)
|
||||
result = {...}
|
||||
bindable:Fire(false)
|
||||
end)
|
||||
|
||||
local ok = bindable.Event:Wait()
|
||||
bindable:Destroy()
|
||||
|
||||
if not ok then
|
||||
error(tostring(result[1]), 2)
|
||||
end
|
||||
|
||||
return unpack(result)
|
||||
elseif self._status == Promise.Status.Resolved then
|
||||
return unpack(self._value)
|
||||
elseif self._status == Promise.Status.Rejected then
|
||||
error(tostring(self._value[1]), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function Promise:_resolve(...)
|
||||
if self._status ~= Promise.Status.Started then
|
||||
return
|
||||
end
|
||||
|
||||
-- If the resolved value was a Promise, we chain onto it!
|
||||
if Promise.is((...)) then
|
||||
-- Without this warning, arguments sometimes mysteriously disappear
|
||||
if select("#", ...) > 1 then
|
||||
local message = ("When returning a Promise from andThen, extra arguments are discarded! See:\n\n%s"):format(
|
||||
self._source
|
||||
)
|
||||
warn(message)
|
||||
end
|
||||
|
||||
(...):andThen(function(...)
|
||||
self:_resolve(...)
|
||||
end, function(...)
|
||||
self:_reject(...)
|
||||
end)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self._status = Promise.Status.Resolved
|
||||
self._value = {...}
|
||||
|
||||
-- We assume that these callbacks will not throw errors.
|
||||
for _, callback in ipairs(self._queuedResolve) do
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
|
||||
function Promise:_reject(...)
|
||||
if self._status ~= Promise.Status.Started then
|
||||
return
|
||||
end
|
||||
|
||||
self._status = Promise.Status.Rejected
|
||||
self._value = {...}
|
||||
|
||||
-- If there are any rejection handlers, call those!
|
||||
if not isEmpty(self._queuedReject) then
|
||||
-- We assume that these callbacks will not throw errors.
|
||||
for _, callback in ipairs(self._queuedReject) do
|
||||
callback(...)
|
||||
end
|
||||
else
|
||||
-- At this point, no one was able to observe the error.
|
||||
-- An error handler might still be attached if the error occurred
|
||||
-- synchronously. We'll wait one tick, and if there are still no
|
||||
-- observers, then we should put a message in the console.
|
||||
|
||||
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
|
||||
TU.RecursiveToString((...)),
|
||||
self._source
|
||||
)
|
||||
warn(message)
|
||||
end
|
||||
end
|
||||
|
||||
return Promise
|
||||
@@ -0,0 +1,331 @@
|
||||
return function()
|
||||
local Promise = require(script.Parent.Promise)
|
||||
|
||||
describe("Promise.new", function()
|
||||
it("should instantiate with a callback", function()
|
||||
local promise = Promise.new(function() end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should invoke the given callback with resolve and reject", function()
|
||||
local callCount = 0
|
||||
local resolveArg
|
||||
local rejectArg
|
||||
|
||||
local promise = Promise.new(function(resolve, reject)
|
||||
callCount = callCount + 1
|
||||
resolveArg = resolve
|
||||
rejectArg = reject
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(resolveArg).to.be.a("function")
|
||||
expect(rejectArg).to.be.a("function")
|
||||
expect(promise._status).to.equal(Promise.Status.Started)
|
||||
end)
|
||||
|
||||
it("should resolve promises on resolve()", function()
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(resolve)
|
||||
callCount = callCount + 1
|
||||
resolve()
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
end)
|
||||
|
||||
it("should reject promises on reject()", function()
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(resolve, reject)
|
||||
callCount = callCount + 1
|
||||
reject()
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
end)
|
||||
|
||||
it("should reject on error in callback", function()
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function()
|
||||
callCount = callCount + 1
|
||||
error("hahah")
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]:find("hahah")).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise.resolve", function()
|
||||
it("should immediately resolve with a value", function()
|
||||
local promise = Promise.resolve(5)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value[1]).to.equal(5)
|
||||
end)
|
||||
|
||||
it("should chain onto passed promises", function()
|
||||
local promise = Promise.resolve(Promise.new(function(_, reject)
|
||||
reject(7)
|
||||
end))
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(7)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise.reject", function()
|
||||
it("should immediately reject with a value", function()
|
||||
local promise = Promise.reject(6)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
end)
|
||||
|
||||
it("should pass a promise as-is as an error", function()
|
||||
local innerPromise = Promise.new(function(resolve)
|
||||
resolve(6)
|
||||
end)
|
||||
|
||||
local promise = Promise.reject(innerPromise)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(innerPromise)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise:andThen", function()
|
||||
it("should chain onto resolved promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local promise = Promise.resolve(5)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end, function()
|
||||
badCallCount = badCallCount + 1
|
||||
end)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(5)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value[1]).to.equal(5)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should chain onto rejected promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local promise = Promise.reject(5)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function(...)
|
||||
badCallCount = badCallCount + 1
|
||||
end, function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(5)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(5)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should chain onto asynchronously resolved promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local startResolution
|
||||
local promise = Promise.new(function(resolve)
|
||||
startResolution = resolve
|
||||
end)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end, function()
|
||||
badCallCount = badCallCount + 1
|
||||
end)
|
||||
|
||||
expect(callCount).to.equal(0)
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
startResolution(6)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(6)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should chain onto asynchronously rejected promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local startResolution
|
||||
local promise = Promise.new(function(_, reject)
|
||||
startResolution = reject
|
||||
end)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function()
|
||||
badCallCount = badCallCount + 1
|
||||
end, function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end)
|
||||
|
||||
expect(callCount).to.equal(0)
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
startResolution(6)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(6)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise.all", function()
|
||||
it("should resolve immediately with an empty table argument", function()
|
||||
local promise = Promise.all({})
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
|
||||
local result = unpack(promise._value)
|
||||
expect(type(result)).to.equal("table")
|
||||
expect(#result).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should resolve immediately with no arguments", function()
|
||||
local promise = Promise.all()
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
|
||||
local result = unpack(promise._value)
|
||||
expect(type(result)).to.equal("table")
|
||||
expect(#result).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should resolve with one result with one resolved argument", function()
|
||||
local promise = Promise.all(Promise.resolve(7))
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
|
||||
local result = unpack(promise._value)
|
||||
expect(type(result)).to.equal("table")
|
||||
expect(#result).to.equal(1)
|
||||
expect(result[1]).to.equal(7)
|
||||
end)
|
||||
|
||||
it("should resolve with multiple resolved arguments", function()
|
||||
local promise = Promise.all(Promise.resolve(7), Promise.resolve(4))
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
|
||||
local result = unpack(promise._value)
|
||||
expect(type(result)).to.equal("table")
|
||||
expect(#result).to.equal(2)
|
||||
expect(result[1]).to.equal(7)
|
||||
expect(result[2]).to.equal(4)
|
||||
end)
|
||||
|
||||
it("should reject with one rejected argument", function()
|
||||
local promise = Promise.all(Promise.reject(5))
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
|
||||
local result = unpack(promise._value)
|
||||
expect(result).to.equal(5)
|
||||
end)
|
||||
|
||||
it("should reject with one success followed by one rejected argument", function()
|
||||
local promise = Promise.all(Promise.resolve(7), Promise.reject(4))
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
|
||||
local result = unpack(promise._value)
|
||||
expect(result).to.equal(4)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
local RetrievalStatus = require(CorePackages.AppTempCommon.LuaApp.Enum.RetrievalStatus)
|
||||
|
||||
local FetchUserFriendsStarted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsStarted)
|
||||
local FetchUserFriendsFailed = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsFailed)
|
||||
local FetchUserFriendsCompleted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsCompleted)
|
||||
|
||||
local function setFieldPerUser(state, fieldName, userId, value)
|
||||
local field = state[fieldName] or {}
|
||||
return Immutable.JoinDictionaries(state, {
|
||||
[fieldName] = Immutable.JoinDictionaries(field, {
|
||||
[userId] = value
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
local function setRetrievalStatus(state, userId, status)
|
||||
return setFieldPerUser(state, "retrievalStatus", userId, status)
|
||||
end
|
||||
|
||||
local function setRetrievalFailureResponse(state, userId, response)
|
||||
return setFieldPerUser(state, "retrievalFailureResponse", userId, response)
|
||||
end
|
||||
|
||||
return function(state, action)
|
||||
state = state or {
|
||||
retrievalStatus = {},
|
||||
retrievalFailureResponse = {},
|
||||
}
|
||||
|
||||
if action.type == FetchUserFriendsStarted.name then
|
||||
state = setRetrievalStatus(state, action.userId, RetrievalStatus.Fetching)
|
||||
elseif action.type == FetchUserFriendsFailed.name then
|
||||
state = setRetrievalStatus(state, action.userId, RetrievalStatus.Failed)
|
||||
state = setRetrievalFailureResponse(state, action.userId, action.response)
|
||||
elseif action.type == FetchUserFriendsCompleted.name then
|
||||
state = setRetrievalStatus(state, action.userId, RetrievalStatus.Done)
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
local ReceivedPlacesInfos = require(CorePackages.AppTempCommon.LuaApp.Actions.ReceivedPlacesInfos)
|
||||
|
||||
return function(state, action)
|
||||
state = state or {}
|
||||
|
||||
if action.type == ReceivedPlacesInfos.name then
|
||||
for _, placeInfo in pairs(action.placesInfos) do
|
||||
state = Immutable.Set(state, tostring(placeInfo.universeId), placeInfo)
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
@@ -0,0 +1,117 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
|
||||
local AddUser = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUser)
|
||||
local AddUsers = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUsers)
|
||||
local ReceivedUserPresence = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedUserPresence)
|
||||
local RemoveUser = require(CorePackages.AppTempCommon.LuaApp.Actions.RemoveUser)
|
||||
local SetUserIsFriend = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserIsFriend)
|
||||
local SetUserMembershipType = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserMembershipType)
|
||||
local SetUserPresence = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserPresence)
|
||||
local SetUserThumbnail = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserThumbnail)
|
||||
|
||||
local FFlagFixUsersReducerDataLoss = settings():GetFFlag("FixUsersReducerDataLoss")
|
||||
|
||||
return function(state, action)
|
||||
state = state or {}
|
||||
|
||||
if action.type == AddUser.name then
|
||||
local user = action.user
|
||||
state = Immutable.Set(state, user.id, user)
|
||||
elseif action.type == AddUsers.name then
|
||||
if FFlagFixUsersReducerDataLoss then
|
||||
local addedUsers = action.users
|
||||
local usersUpdate = {}
|
||||
for userId, addedUser in pairs(addedUsers) do
|
||||
local existingUser = state[userId]
|
||||
if existingUser then
|
||||
usersUpdate[userId] = Immutable.JoinDictionaries(existingUser, addedUser)
|
||||
else
|
||||
usersUpdate[userId] = addedUser
|
||||
end
|
||||
end
|
||||
|
||||
state = Immutable.JoinDictionaries(state, usersUpdate)
|
||||
else
|
||||
local users = action.users
|
||||
state = Immutable.JoinDictionaries(state, users)
|
||||
end
|
||||
|
||||
elseif action.type == SetUserIsFriend.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
local newUser = Immutable.Set(user, "isFriend", action.isFriend)
|
||||
state = Immutable.Set(state, user.id, newUser)
|
||||
else
|
||||
warn("Setting isFriend on user", action.userId, "who doesn't exist yet")
|
||||
end
|
||||
elseif action.type == SetUserPresence.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
local newUser = Immutable.JoinDictionaries(user, {
|
||||
presence = action.presence,
|
||||
lastLocation = action.lastLocation,
|
||||
})
|
||||
state = Immutable.Set(state, user.id, newUser)
|
||||
else
|
||||
warn("Setting presence on user", action.userId, "who doesn't exist yet")
|
||||
end
|
||||
elseif action.type == ReceivedUserPresence.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
presence = action.presence,
|
||||
lastLocation = action.lastLocation,
|
||||
placeId = action.placeId,
|
||||
rootPlaceId = action.rootPlaceId,
|
||||
gameInstanceId = action.gameInstanceId,
|
||||
lastOnline = action.lastOnline,
|
||||
universeId = action.universeId,
|
||||
}),
|
||||
})
|
||||
end
|
||||
elseif action.type == SetUserThumbnail.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
if FFlagFixUsersReducerDataLoss then
|
||||
local thumbnails = user.thumbnails or {}
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
thumbnails = Immutable.JoinDictionaries(thumbnails, {
|
||||
[action.thumbnailType] = Immutable.JoinDictionaries(thumbnails[action.thumbnailType] or {}, {
|
||||
[action.thumbnailSize] = action.image,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
else
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
thumbnails = Immutable.JoinDictionaries(user.thumbnails, {
|
||||
[action.thumbnailType] = Immutable.JoinDictionaries(user.thumbnails[action.thumbnailType] or {}, {
|
||||
[action.thumbnailSize] = action.image,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
end
|
||||
elseif action.type == SetUserMembershipType.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
membership = action.membershipType,
|
||||
}),
|
||||
})
|
||||
end
|
||||
elseif action.type == RemoveUser.name then
|
||||
if state[action.userId] then
|
||||
state = Immutable.RemoveFromDictionary(state, action.userId)
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local MockId = require(CorePackages.AppTempCommon.LuaApp.MockId)
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
local Users = require(CorePackages.AppTempCommon.LuaApp.Reducers.Users)
|
||||
|
||||
local AddUser = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUser)
|
||||
local ReceivedUserPresence = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedUserPresence)
|
||||
local SetUserIsFriend = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserIsFriend)
|
||||
local SetUserMembershipType = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserMembershipType)
|
||||
local SetUserPresence = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserPresence)
|
||||
|
||||
describe("initial state", function()
|
||||
it("should return an initial table when passed nil", function()
|
||||
local state = Users(nil, {})
|
||||
expect(state).to.be.a("table")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("AddUser", function()
|
||||
it("should add a user to the store", function()
|
||||
local user = User.mock()
|
||||
local state = {}
|
||||
|
||||
state = Users(state, AddUser(user))
|
||||
|
||||
expect(state[user.id]).to.equal(user)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("SetUserIsFriend", function()
|
||||
it("should set isFriend on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
expect(state[user.id].isFriend).to.equal(false)
|
||||
|
||||
state = Users(state, SetUserIsFriend(user.id, true))
|
||||
expect(state[user.id].isFriend).to.equal(true)
|
||||
|
||||
state = Users(state, SetUserIsFriend(user.id, false))
|
||||
expect(state[user.id].isFriend).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("SetUserPresence", function()
|
||||
it("should set presence on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.OFFLINE)
|
||||
|
||||
state = Users(state, SetUserPresence(user.id, User.PresenceType.ONLINE))
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.ONLINE)
|
||||
|
||||
state = Users(state, SetUserPresence(user.id, User.PresenceType.IN_GAME))
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.IN_GAME)
|
||||
|
||||
state = Users(state, SetUserPresence(user.id, User.PresenceType.IN_STUDIO))
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.IN_STUDIO)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ReceivedUserPresence", function()
|
||||
it("should set presence on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
local existingPresence = user.presence
|
||||
local newPresence = 'ONLINE'
|
||||
local lastLocation = MockId()
|
||||
local newPlaceId = MockId()
|
||||
|
||||
state = Users(state, ReceivedUserPresence(user.id, newPresence, lastLocation, newPlaceId))
|
||||
|
||||
expect(user.presence).to.equal(existingPresence)
|
||||
expect(state[user.id].presence).to.equal(newPresence)
|
||||
expect(state[user.id].lastLocation).to.equal(lastLocation)
|
||||
expect(state[user.id].placeId).to.equal(newPlaceId)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("SetUserMembershipType", function()
|
||||
it("should set membership on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
local existingMembership = user.membership
|
||||
local newMembership = Enum.MembershipType.BuildersClub
|
||||
|
||||
state = Users(state, SetUserMembershipType(user.id, newMembership))
|
||||
|
||||
expect(user.membership).to.equal(existingMembership)
|
||||
expect(state[user.id].membership).to.equal(newMembership)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
local Result = {}
|
||||
Result.__index = Result
|
||||
|
||||
local ResultTypeSymbol = newproxy(true)
|
||||
|
||||
function Result.new(status, value)
|
||||
assert(typeof(status) == "boolean")
|
||||
|
||||
local result = {
|
||||
-- Used to locate where a result was created
|
||||
_source = debug.traceback(),
|
||||
|
||||
-- A tag to identify us as a result
|
||||
[ResultTypeSymbol] = true,
|
||||
|
||||
_status = status,
|
||||
|
||||
-- The value success value or error message.
|
||||
_value = value,
|
||||
}
|
||||
|
||||
setmetatable(result, Result)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function Result.success(value)
|
||||
return Result.new(true, value)
|
||||
end
|
||||
|
||||
function Result.error(value)
|
||||
return Result.new(false, value)
|
||||
end
|
||||
|
||||
--[[
|
||||
Is the given object a Result instance?
|
||||
]]
|
||||
function Result.is(object)
|
||||
if typeof(object) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
return object[ResultTypeSymbol]
|
||||
end
|
||||
|
||||
--[[
|
||||
The given callbacks are invoked depending on that result.
|
||||
|
||||
Creates a new result that receives the output of the callback.
|
||||
]]
|
||||
function Result:match(successHandler, errorHandler)
|
||||
assert(typeof(successHandler) == "function" or typeof(successHandler) == "nil",
|
||||
string.format("Result:match expects successHandler to be a function or nil, got %s", typeof(successHandler)))
|
||||
assert(typeof(errorHandler) == "function" or typeof(errorHandler) == "nil",
|
||||
string.format("Result:match expects errorHandler to be a function or nil, got %s", typeof(errorHandler)))
|
||||
|
||||
local newResult
|
||||
if self._status then
|
||||
if successHandler ~= nil then
|
||||
newResult = successHandler(self._value)
|
||||
else
|
||||
return self
|
||||
end
|
||||
else
|
||||
if errorHandler ~= nil then
|
||||
newResult = errorHandler(self._value)
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
if Result.is(newResult) then
|
||||
return newResult
|
||||
else
|
||||
return Result.success(newResult)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
The given callback is invoked if the result is success.
|
||||
|
||||
Creates a new result that receives the output of the callback.
|
||||
]]
|
||||
function Result:matchSuccess(successHandler)
|
||||
return self:match(successHandler, nil)
|
||||
end
|
||||
|
||||
--[[
|
||||
The given callback is invoked if the result is error.
|
||||
|
||||
Creates a new result that receives the output of the callback.
|
||||
]]
|
||||
function Result:matchError(errorHandler)
|
||||
return self:match(nil, errorHandler)
|
||||
end
|
||||
|
||||
function Result:unwrap()
|
||||
return self._status, self._value
|
||||
end
|
||||
|
||||
return Result
|
||||
@@ -0,0 +1,217 @@
|
||||
return function()
|
||||
local Result = require(script.Parent.Result)
|
||||
|
||||
describe("Constructors", function()
|
||||
it("should return a success result from Result.new with a success value", function()
|
||||
local success, value = Result.new(true, "foo"):unwrap()
|
||||
expect(success).to.equal(true)
|
||||
expect(value).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("should return an error result from Result.new with a failure value", function()
|
||||
local success, value = Result.new(false, "foo"):unwrap()
|
||||
expect(success).to.equal(false)
|
||||
expect(value).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("should return a success result from Result.success", function()
|
||||
local success, value = Result.success("foo"):unwrap()
|
||||
expect(success).to.equal(true)
|
||||
expect(value).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("should return an error result from Result.error", function()
|
||||
local success, value = Result.error("foo"):unwrap()
|
||||
expect(success).to.equal(false)
|
||||
expect(value).to.equal("foo")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Result:match", function()
|
||||
it("should call the first callback with the value if it's a success result", function()
|
||||
local called = false
|
||||
Result.success("foo"):match(
|
||||
function(value)
|
||||
called = true
|
||||
expect(value).to.equal("foo")
|
||||
end,
|
||||
function(value)
|
||||
assert(false)
|
||||
end
|
||||
)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should call the second callback with the error if it's an error result", function()
|
||||
local called = false
|
||||
Result.error("foo"):match(
|
||||
function(value)
|
||||
assert(false)
|
||||
end,
|
||||
function(value)
|
||||
expect(value).to.equal("foo")
|
||||
called = true
|
||||
end
|
||||
)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should return the result of the first callback if it's a result", function()
|
||||
Result.success("foo"):match(
|
||||
function()
|
||||
return Result.success("bar")
|
||||
end,
|
||||
nil
|
||||
):match(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end,
|
||||
nil
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return the result of the second callback if it's a result", function()
|
||||
Result.error("foo"):match(
|
||||
nil,
|
||||
function()
|
||||
return Result.success("bar")
|
||||
end
|
||||
):match(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end,
|
||||
nil
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return self if it's success and the first callback isn't provided", function()
|
||||
local result1 = Result.success("foo")
|
||||
local result2 = result1:match(nil, nil)
|
||||
expect(result1).to.equal(result2)
|
||||
end)
|
||||
|
||||
it("should return self if it's error and the second callback isn't provided", function()
|
||||
local result1 = Result.error("foo")
|
||||
local result2 = result1:match(nil, nil)
|
||||
expect(result1).to.equal(result2)
|
||||
end)
|
||||
|
||||
it("should return success result wrapping value returned by first callback if not a result", function()
|
||||
Result.success("foo"):match(
|
||||
function()
|
||||
return "bar"
|
||||
end,
|
||||
nil
|
||||
):match(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end,
|
||||
nil
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return success result wrapping value returned by second callback if not a result", function()
|
||||
Result.error("foo"):match(
|
||||
nil,
|
||||
function()
|
||||
return "bar"
|
||||
end
|
||||
):match(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end,
|
||||
nil
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Result:matchSuccess", function()
|
||||
it("should call the callback with the value if it's a success result", function()
|
||||
local called = false
|
||||
Result.success("foo"):matchSuccess(
|
||||
function(value)
|
||||
expect(value).to.equal("foo")
|
||||
called = true
|
||||
end
|
||||
)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should not call the callback if it's an error result", function()
|
||||
Result.error("foo"):matchSuccess(
|
||||
function(value)
|
||||
assert(false)
|
||||
end
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return the result of the callback if it's a result", function()
|
||||
Result.success("foo"):matchSuccess(
|
||||
function()
|
||||
return Result.success("bar")
|
||||
end
|
||||
):matchSuccess(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return success result wrapping value returned by callback if not a result", function()
|
||||
Result.success("foo"):matchSuccess(
|
||||
function()
|
||||
return "bar"
|
||||
end
|
||||
):matchSuccess(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Result:matchError", function()
|
||||
it("should call the callback with the value if it's an error result", function()
|
||||
local called = false
|
||||
Result.error("foo"):matchError(
|
||||
function(value)
|
||||
expect(value).to.equal("foo")
|
||||
called = true
|
||||
end
|
||||
)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should not call the callback if it's a success result", function()
|
||||
Result.success("foo"):matchError(
|
||||
function(value)
|
||||
assert(false)
|
||||
end
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return the result of the callback if it's a result", function()
|
||||
Result.error("foo"):matchError(
|
||||
function()
|
||||
return Result.success("bar")
|
||||
end
|
||||
):matchSuccess(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end
|
||||
)
|
||||
end)
|
||||
|
||||
it("should return success result wrapping value returned by callback if not a result", function()
|
||||
Result.error("foo"):matchError(
|
||||
function()
|
||||
return "bar"
|
||||
end
|
||||
):matchSuccess(
|
||||
function(value)
|
||||
expect(value).to.equal("bar")
|
||||
end
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,184 @@
|
||||
--[[
|
||||
Provides functions for comparing and printing lua tables.
|
||||
]]
|
||||
|
||||
local TableUtilities = {}
|
||||
local defaultIgnore = {}
|
||||
|
||||
--[[
|
||||
Takes two tables A and B, returns if they have the same key-value pairs
|
||||
Except ignored keys
|
||||
]]
|
||||
function TableUtilities.ShallowEqual(A, B, ignore)
|
||||
if not A or not B then
|
||||
return false
|
||||
elseif A == B then
|
||||
return true
|
||||
end
|
||||
|
||||
if not ignore then
|
||||
ignore = defaultIgnore
|
||||
end
|
||||
|
||||
for key, value in pairs(A) do
|
||||
if B[key] ~= value and not ignore[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for key, value in pairs(B) do
|
||||
if A[key] ~= value and not ignore[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
Takes two tables A, B and a key, returns if two tables have the same value at key
|
||||
]]
|
||||
function TableUtilities.EqualKey(A, B, key)
|
||||
if A and B and key and key ~= "" and A[key] and B[key] and A[key] == B[key] then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--[[
|
||||
Takes two tables A and B, returns a new table with elements of A
|
||||
which are either not keys in B or have a different value in B
|
||||
]]
|
||||
function TableUtilities.TableDifference(A, B)
|
||||
local new = {}
|
||||
|
||||
for key, value in pairs(A) do
|
||||
if B[key] ~= A[key] then
|
||||
new[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
return new
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Takes a list and returns a table whose
|
||||
keys are elements of the list and whose
|
||||
values are all true
|
||||
]]
|
||||
local function membershipTable(list)
|
||||
local result = {}
|
||||
for i = 1, #list do
|
||||
result[list[i]] = true
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Takes a table and returns a list of keys in that table
|
||||
]]
|
||||
local function listOfKeys(t)
|
||||
local result = {}
|
||||
for key,_ in pairs(t) do
|
||||
table.insert(result, key)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Takes two lists A and B, returns a new list of elements of A
|
||||
which are not in B
|
||||
]]
|
||||
function TableUtilities.ListDifference(A, B)
|
||||
return listOfKeys(TableUtilities.TableDifference(membershipTable(A), membershipTable(B)))
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
For debugging. Returns false if the given table has any of the following:
|
||||
- a key that is neither a number or a string
|
||||
- a mix of number and string keys
|
||||
- number keys which are not exactly 1..#t
|
||||
]]
|
||||
function TableUtilities.CheckListConsistency(t)
|
||||
local containsNumberKey = false
|
||||
local containsStringKey = false
|
||||
local numberConsistency = true
|
||||
|
||||
local index = 1
|
||||
for x, _ in pairs(t) do
|
||||
if type(x) == 'string' then
|
||||
containsStringKey = true
|
||||
elseif type(x) == 'number' then
|
||||
if index ~= x then
|
||||
numberConsistency = false
|
||||
end
|
||||
containsNumberKey = true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
if containsStringKey and containsNumberKey then
|
||||
return false
|
||||
end
|
||||
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
if containsNumberKey then
|
||||
return numberConsistency
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
For debugging, serializes the given table to a reasonable string that might even interpret as lua.
|
||||
]]
|
||||
function TableUtilities.RecursiveToString(t, indent)
|
||||
indent = indent or ''
|
||||
|
||||
if type(t) == 'table' then
|
||||
local result = ""
|
||||
if not TableUtilities.CheckListConsistency(t) then
|
||||
result = result .. "-- WARNING: this table fails the list consistency test\n"
|
||||
end
|
||||
result = result .. "{\n"
|
||||
for k,v in pairs(t) do
|
||||
if type(k) == 'string' then
|
||||
result = result
|
||||
.. " "
|
||||
.. indent
|
||||
.. tostring(k)
|
||||
.. " = "
|
||||
.. TableUtilities.RecursiveToString(v, " "..indent)
|
||||
..";\n"
|
||||
end
|
||||
if type(k) == 'number' then
|
||||
result = result .. " " .. indent .. TableUtilities.RecursiveToString(v, " "..indent)..",\n"
|
||||
end
|
||||
end
|
||||
result = result .. indent .. "}"
|
||||
return result
|
||||
else
|
||||
return tostring(t)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Takes a table and returns the field count
|
||||
]]
|
||||
function TableUtilities.FieldCount(t)
|
||||
local fieldCount = 0
|
||||
for _ in pairs(t) do
|
||||
fieldCount = fieldCount + 1
|
||||
end
|
||||
return fieldCount
|
||||
end
|
||||
|
||||
return TableUtilities
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
return function()
|
||||
local TableUtilities = require(script.Parent.TableUtilities)
|
||||
|
||||
it("should return whether tables are equal to each other", function()
|
||||
local tableA = nil
|
||||
local tableB = nil
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = nil
|
||||
tableB = {}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = nil
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = {}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(true)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(true)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value2",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value1",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should return whether tables are equal to each other at key", function()
|
||||
local tableA = nil
|
||||
local tableB = nil
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = nil
|
||||
tableB = {}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = nil
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = {}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(true)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value2",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value1",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(true)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key2")).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should return table's field count", function()
|
||||
local table = {}
|
||||
expect(TableUtilities.FieldCount(table)).to.equal(0)
|
||||
|
||||
table = {
|
||||
key1 = "value1",
|
||||
}
|
||||
expect(TableUtilities.FieldCount(table)).to.equal(1)
|
||||
|
||||
table = {
|
||||
key1 = "value1",
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.FieldCount(table)).to.equal(2)
|
||||
end)
|
||||
end
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local LuaApp = CorePackages.AppTempCommon.LuaApp
|
||||
|
||||
local GamesGetThumbnails = require(LuaApp.Http.Requests.GamesGetThumbnails)
|
||||
local SetGameThumbnails = require(LuaApp.Actions.SetGameThumbnails)
|
||||
|
||||
local Functional = require(CorePackages.AppTempCommon.Common.Functional)
|
||||
local Promise = require(LuaApp.Promise)
|
||||
local Result = require(LuaApp.Result)
|
||||
|
||||
local TableUtilities = require(LuaApp.TableUtilities)
|
||||
|
||||
local THUMBNAIL_PAGE_COUNT = 20
|
||||
local THUMBNAIL_SIZE = 150
|
||||
local RETRY_MAX_COUNT = math.max(0, settings():GetFVariable("LuaAppNonFinalThumbnailMaxRetries"))
|
||||
local RETRY_TIME_MULTIPLIER = 2 -- seconds
|
||||
|
||||
local function convertToId(value)
|
||||
if type(value) ~= "number" and type(value) ~= "string" then
|
||||
return Result.error("convertToId expects value passed in to be a number or a string")
|
||||
end
|
||||
|
||||
return Result.success(tostring(value))
|
||||
end
|
||||
|
||||
local function subdivideThumbnailTokenArray(thumbnailTokens, tokenLimit)
|
||||
local someTokens = {}
|
||||
for i = 1, #thumbnailTokens, tokenLimit do
|
||||
local subArray = Functional.Take(thumbnailTokens, tokenLimit, i)
|
||||
table.insert(someTokens, subArray)
|
||||
end
|
||||
|
||||
return someTokens
|
||||
end
|
||||
|
||||
local function fetchThumbnailBatch(networkImpl, store, thumbnailTokens)
|
||||
return GamesGetThumbnails(networkImpl, thumbnailTokens, THUMBNAIL_SIZE, THUMBNAIL_SIZE):andThen(function(result)
|
||||
local thumbnails = {}
|
||||
local unfinalizedThumbnails = {}
|
||||
|
||||
for _,image in pairs(result.responseBody) do
|
||||
local convertToIdResult = convertToId(image.universeId)
|
||||
|
||||
convertToIdResult:match(function(universeId)
|
||||
if image.final == false then
|
||||
unfinalizedThumbnails[universeId] = image.retryToken
|
||||
else
|
||||
-- index all of the thumbnails by universeId
|
||||
thumbnails[universeId] = image
|
||||
end
|
||||
end, function(convertToIdError)
|
||||
warn(convertToIdError)
|
||||
end)
|
||||
end
|
||||
|
||||
store:dispatch(SetGameThumbnails(thumbnails))
|
||||
return Promise.resolve(unfinalizedThumbnails)
|
||||
end)
|
||||
end
|
||||
|
||||
local function fetchSubdividedThumbnailsArray(networkImpl, store, thumbnailTokens)
|
||||
return fetchThumbnailBatch(networkImpl, store, thumbnailTokens):andThen(function(unfinalizedThumbnails)
|
||||
local remainingUnfinalizedThumbnails = unfinalizedThumbnails
|
||||
|
||||
for retryCount = 1, RETRY_MAX_COUNT do
|
||||
if TableUtilities.FieldCount(remainingUnfinalizedThumbnails) == 0 then
|
||||
return -- Bail out, we're done!
|
||||
end
|
||||
|
||||
wait(RETRY_TIME_MULTIPLIER * math.pow(2, retryCount - 1))
|
||||
remainingUnfinalizedThumbnails = fetchThumbnailBatch(networkImpl, store, remainingUnfinalizedThumbnails):await()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function fetchThumbnails(networkImpl, thumbnailTokens)
|
||||
return function(store)
|
||||
-- NOTE : because the size of each thumbnail token, me must limit the number we can fetch at a time.
|
||||
-- So break apart the array of tokens we get into smaller, more manageable pieces.
|
||||
local fetchPromises = {}
|
||||
local someTokens = subdivideThumbnailTokenArray(thumbnailTokens, THUMBNAIL_PAGE_COUNT)
|
||||
for _, thumbsArr in ipairs(someTokens) do
|
||||
local promise = fetchSubdividedThumbnailsArray(networkImpl, store, thumbsArr)
|
||||
table.insert(fetchPromises, promise)
|
||||
end
|
||||
|
||||
return Promise.all(fetchPromises)
|
||||
end
|
||||
end
|
||||
|
||||
return fetchThumbnails
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local ApiFetchGameThumbnails = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchGameThumbnails)
|
||||
local Functional = require(CorePackages.AppTempCommon.Common.Functional)
|
||||
local GamesMultigetPlaceDetails = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.GamesMultigetPlaceDetails)
|
||||
local PlaceInfoModel = require(CorePackages.AppTempCommon.LuaChat.Models.PlaceInfoModel)
|
||||
local ReceivedPlacesInfos = require(CorePackages.AppTempCommon.LuaApp.Actions.ReceivedPlacesInfos)
|
||||
|
||||
return function(networkImpl, placeIds)
|
||||
return function(store)
|
||||
if not placeIds or #placeIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
return GamesMultigetPlaceDetails(networkImpl, placeIds):andThen(function(result)
|
||||
local data = result.responseBody
|
||||
|
||||
local imageTokens = {}
|
||||
local placeInfos = Functional.Map(data, function(placeInfoData)
|
||||
local placeInfo = PlaceInfoModel.fromWeb(placeInfoData)
|
||||
|
||||
local universePlaceInfos = store:getState().UniversePlaceInfos
|
||||
local universeId = placeInfo.universeRootPlaceId
|
||||
if not universePlaceInfos[universeId] or placeInfo.imageToken ~= universePlaceInfos[universeId].imageToken then
|
||||
table.insert(imageTokens, placeInfo.imageToken)
|
||||
end
|
||||
|
||||
return placeInfo
|
||||
end)
|
||||
|
||||
store:dispatch(ReceivedPlacesInfos(placeInfos))
|
||||
|
||||
if #imageTokens > 0 then
|
||||
store:dispatch(ApiFetchGameThumbnails(networkImpl, imageTokens))
|
||||
end
|
||||
|
||||
return placeInfos
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Functional = require(CorePackages.AppTempCommon.Common.Functional)
|
||||
local GetPlaceInfos = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.GetPlaceInfos)
|
||||
|
||||
-- LuaChat
|
||||
local PlaceInfoModel = require(CorePackages.AppTempCommon.LuaChat.Models.PlaceInfoModel)
|
||||
local ReceivedMultiplePlaceInfos = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedMultiplePlaceInfos)
|
||||
|
||||
return function(networkImpl, placeIds)
|
||||
return function(store)
|
||||
return GetPlaceInfos(networkImpl, placeIds):andThen(function(result)
|
||||
local data = result.responseBody
|
||||
|
||||
local placeInfos = Functional.Map(data, function(placeInfoData)
|
||||
return PlaceInfoModel.fromWeb(placeInfoData)
|
||||
end)
|
||||
|
||||
store:dispatch(ReceivedMultiplePlaceInfos(placeInfos))
|
||||
|
||||
return placeInfos
|
||||
end)
|
||||
end
|
||||
end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Actions = CorePackages.AppTempCommon.LuaApp.Actions
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local UsersGetFriendCount = require(Requests.UsersGetFriendCount)
|
||||
local SetFriendCount = require(Actions.SetFriendCount)
|
||||
|
||||
return function(networkImpl)
|
||||
return function(store)
|
||||
return UsersGetFriendCount(networkImpl):andThen(function(result)
|
||||
local data = result.responseBody
|
||||
|
||||
if data.success and data.count then
|
||||
store:dispatch(SetFriendCount(data.count))
|
||||
end
|
||||
|
||||
return data.count
|
||||
end)
|
||||
end
|
||||
end
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
local ApiFetchUsersPresences = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchUsersPresences)
|
||||
local ApiFetchUsersThumbnail = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchUsersThumbnail)
|
||||
local ApiFetchUsersFriendCount = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchUsersFriendCount)
|
||||
local UsersGetFriends = require(Requests.UsersGetFriends)
|
||||
|
||||
local AddUsers = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUsers)
|
||||
local FetchUserFriendsStarted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsStarted)
|
||||
local FetchUserFriendsFailed = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsFailed)
|
||||
local FetchUserFriendsCompleted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsCompleted)
|
||||
local UserModel = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
local UpdateUsers = require(CorePackages.AppTempCommon.LuaApp.Thunks.UpdateUsers)
|
||||
|
||||
local LuaAppRemoveGetFriendshipCountApiCalls = settings():GetFFlag("LuaAppRemoveGetFriendshipCountApiCalls")
|
||||
local homePageDataFetchRefactor = settings():GetFFlag('LuaHomePageDataFetchRefactor')
|
||||
|
||||
return function(requestImpl, userId, thumbnailRequest)
|
||||
return function(store)
|
||||
store:dispatch(FetchUserFriendsStarted(userId))
|
||||
if not LuaAppRemoveGetFriendshipCountApiCalls then
|
||||
store:dispatch(ApiFetchUsersFriendCount(requestImpl))
|
||||
end
|
||||
return UsersGetFriends(requestImpl, userId):andThen(function(response)
|
||||
local responseBody = response.responseBody
|
||||
|
||||
local userIds = {}
|
||||
local newUsers = {}
|
||||
for _, userData in pairs(responseBody.data) do
|
||||
local id = tostring(userData.id)
|
||||
local newUser = UserModel.fromData(id, userData.name, true)
|
||||
|
||||
table.insert(userIds, id)
|
||||
newUsers[newUser.id] = newUser
|
||||
end
|
||||
if LuaAppRemoveGetFriendshipCountApiCalls then
|
||||
store:dispatch(UpdateUsers(newUsers))
|
||||
else
|
||||
store:dispatch(AddUsers(newUsers))
|
||||
end
|
||||
return userIds
|
||||
end):andThen(function(userIds)
|
||||
-- Asynchronously fetch friend thumbnails so we don't block display of UI
|
||||
store:dispatch(ApiFetchUsersThumbnail(requestImpl, userIds, thumbnailRequest))
|
||||
return store:dispatch(ApiFetchUsersPresences(requestImpl, userIds))
|
||||
end):andThen(
|
||||
function(result)
|
||||
store:dispatch(FetchUserFriendsCompleted(userId))
|
||||
if homePageDataFetchRefactor then
|
||||
return Promise.resolve(result)
|
||||
end
|
||||
end,
|
||||
function(response)
|
||||
store:dispatch(FetchUserFriendsFailed(userId, response))
|
||||
if homePageDataFetchRefactor then
|
||||
return Promise.reject(response)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Utils = CorePackages.AppTempCommon.LuaChat.Utils
|
||||
|
||||
local getPlaceIds = require(Utils.getFriendsActiveGamesPlaceIdsFromUsersPresence)
|
||||
local receiveUsersPresence = require(Utils.receiveUsersPresence)
|
||||
|
||||
local ApiFetchGamesDataByPlaceIds = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchGamesDataByPlaceIds)
|
||||
local UsersGetPresence = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.UsersGetPresence)
|
||||
|
||||
local FFlagLuaHomeGetFriendsPlayingGamesInfo = settings():GetFFlag("LuaHomeGetFriendsPlayingGamesInfo")
|
||||
|
||||
return function(networkImpl, userIds)
|
||||
return function(store)
|
||||
return UsersGetPresence(networkImpl, userIds):andThen(function(result)
|
||||
local userPresences = result.responseBody.userPresences
|
||||
receiveUsersPresence(userPresences, store)
|
||||
|
||||
if FFlagLuaHomeGetFriendsPlayingGamesInfo then
|
||||
local placeIds = getPlaceIds(userPresences, store)
|
||||
|
||||
store:dispatch(ApiFetchGamesDataByPlaceIds(networkImpl, placeIds))
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Actions = CorePackages.AppTempCommon.LuaApp.Actions
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local UsersGetThumbnail = require(Requests.UsersGetThumbnail)
|
||||
local SetUserThumbnail = require(Actions.SetUserThumbnail)
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
|
||||
local function fetchThumbnailsBatch(networkImpl, userIds, thumbnailRequest)
|
||||
local fetchedPromises = {}
|
||||
|
||||
for _, userId in pairs(userIds) do
|
||||
table.insert(fetchedPromises,
|
||||
UsersGetThumbnail(userId, thumbnailRequest.thumbnailType, thumbnailRequest.thumbnailSize)
|
||||
)
|
||||
end
|
||||
|
||||
return Promise.all(fetchedPromises)
|
||||
end
|
||||
|
||||
return function(networkImpl, userIds, thumbnailRequests)
|
||||
return function(store)
|
||||
-- We currently cannot batch request user avatar thumbnails,
|
||||
-- so each thumbnailRequest has to be processed individually.
|
||||
|
||||
local fetchedPromises = {}
|
||||
for _, thumbnailRequest in pairs(thumbnailRequests) do
|
||||
table.insert(fetchedPromises,
|
||||
fetchThumbnailsBatch(networkImpl, userIds, thumbnailRequest):andThen(function(result)
|
||||
for _, data in pairs(result) do
|
||||
store:dispatch(SetUserThumbnail(data.id, data.image, data.thumbnailType, data.thumbnailSize))
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
|
||||
return Promise.all(fetchedPromises)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local AppTempCommon = CorePackages.AppTempCommon
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local ChatSendMessage = require(Requests.ChatSendMessage)
|
||||
local ChatStartOneToOneConversation = require(Requests.ChatStartOneToOneConversation)
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
local trimCharacterFromEndString = require(AppTempCommon.Temp.trimCharacterFromEndString)
|
||||
|
||||
local INVITE_TEXT_MESSAGE = "Come join me in %s"
|
||||
local INVITE_LINK_MESSAGE = "%s/games/%s"
|
||||
|
||||
return function(networkImpl, userId, placeInfo)
|
||||
local clientId = Players.LocalPlayer.UserId
|
||||
|
||||
-- Construct the invite messages based on place info
|
||||
local inviteTextMessage = string.format(INVITE_TEXT_MESSAGE, placeInfo.name)
|
||||
|
||||
local trimmedUrl = trimCharacterFromEndString(Url.WWW_URL, "/")
|
||||
local inviteLinkMessage = string.format(INVITE_LINK_MESSAGE, trimmedUrl, placeInfo.universeRootPlaceId)
|
||||
|
||||
return function(store)
|
||||
return ChatStartOneToOneConversation(networkImpl, userId, clientId):andThen(function(conversationResult)
|
||||
local conversation = conversationResult.responseBody.conversation
|
||||
|
||||
return ChatSendMessage(networkImpl, conversation.id, inviteTextMessage):andThen(function()
|
||||
return ChatSendMessage(networkImpl, conversation.id, inviteLinkMessage):andThen(function(inviteResult)
|
||||
local data = inviteResult.responseBody
|
||||
|
||||
return {
|
||||
resultType = data.resultType,
|
||||
conversationId = conversation.id,
|
||||
placeId = placeInfo.universeRootPlaceId,
|
||||
}
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
local AddUsers = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUsers)
|
||||
local SetFriendCount = require(CorePackages.AppTempCommon.LuaApp.Actions.SetFriendCount)
|
||||
|
||||
return function(users)
|
||||
return function(store)
|
||||
local friendCountOffset = 0
|
||||
local updatedUsers = {}
|
||||
|
||||
for _, user in pairs(users) do
|
||||
local needsUpdate = false
|
||||
local userId = user.id
|
||||
local isFriend = user.isFriend
|
||||
local offset = 0
|
||||
|
||||
assert(typeof(isFriend) == "boolean")
|
||||
|
||||
local userInStore = store:getState().Users[userId]
|
||||
if userInStore then
|
||||
-- Mark user with needsUpdate if any of the field is different
|
||||
-- from the existing user information in Store.
|
||||
if not User.compare(userInStore, user) then
|
||||
needsUpdate = true
|
||||
if userInStore.isFriend ~= isFriend then
|
||||
offset = isFriend and 1 or -1
|
||||
end
|
||||
end
|
||||
else
|
||||
needsUpdate = true
|
||||
offset = isFriend and 1 or 0
|
||||
end
|
||||
|
||||
if needsUpdate then
|
||||
friendCountOffset = friendCountOffset + offset
|
||||
updatedUsers[userId] = user
|
||||
end
|
||||
end
|
||||
|
||||
if next(updatedUsers) then
|
||||
store:dispatch(AddUsers(updatedUsers))
|
||||
end
|
||||
|
||||
if friendCountOffset ~= 0 then
|
||||
local currentFriendCount = store:getState().FriendCount
|
||||
store:dispatch(SetFriendCount(currentFriendCount + friendCountOffset))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,123 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
|
||||
local UpdateUsers = require(CorePackages.AppTempCommon.LuaApp.Thunks.UpdateUsers)
|
||||
|
||||
local AddUsers = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUsers)
|
||||
local SetFriendCount = require(CorePackages.AppTempCommon.LuaApp.Actions.SetFriendCount)
|
||||
|
||||
local FriendCount = require(CorePackages.AppTempCommon.LuaChat.Reducers.FriendCount)
|
||||
local Users = require(CorePackages.AppTempCommon.LuaApp.Reducers.Users)
|
||||
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
|
||||
local function UsersReducerMonitor (state, action)
|
||||
state = state or {
|
||||
numberOfAddUsersCalled = 0,
|
||||
numberOfUsersPassedIn = 0,
|
||||
}
|
||||
|
||||
if action.type == AddUsers.name then
|
||||
state.numberOfAddUsersCalled = state.numberOfAddUsersCalled + 1
|
||||
state.numberOfUsersPassedIn = 0
|
||||
for _, _ in pairs(action.users) do
|
||||
state.numberOfUsersPassedIn = state.numberOfUsersPassedIn + 1
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
local function FriendCountReducerMonitor (state, action)
|
||||
state = state or {
|
||||
numberOfSetFriendCountCalled = 0,
|
||||
}
|
||||
|
||||
if action.type == SetFriendCount.name then
|
||||
state.numberOfSetFriendCountCalled = state.numberOfSetFriendCountCalled + 1
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
local function CustomReducer(state, action)
|
||||
state = state or {}
|
||||
|
||||
return {
|
||||
Users = Users(state.Users, action),
|
||||
UsersReducerMonitor = UsersReducerMonitor(state.UsersReducerMonitor, action),
|
||||
|
||||
FriendCount = FriendCount(state.FriendCount, action),
|
||||
FriendCountReducerMonitor = FriendCountReducerMonitor(state.FriendCountReducerMonitor, action),
|
||||
}
|
||||
end
|
||||
|
||||
local listOfUsers = {
|
||||
["1"] = User.fromData(1, "Hedonism Bot", true),
|
||||
["2"] = User.fromData(2, "Hypno Toad", true),
|
||||
["3"] = User.fromData(3, "John Zoidberg", false),
|
||||
["4"] = User.fromData(4, "Pazuzu", true),
|
||||
["5"] = User.fromData(5, "Ogden Wernstrom", true),
|
||||
["6"] = User.fromData(6, "Lrrr", true),
|
||||
}
|
||||
|
||||
it("should do nothing if empty list of users is provided", function()
|
||||
local store = Rodux.Store.new(CustomReducer, {}, {
|
||||
Rodux.thunkMiddleware,
|
||||
})
|
||||
store:dispatch(UpdateUsers({ }))
|
||||
|
||||
local state = store:getState()
|
||||
|
||||
expect(state.UsersReducerMonitor.numberOfAddUsersCalled).to.equal(0)
|
||||
expect(state.FriendCountReducerMonitor.numberOfSetFriendCountCalled).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should update only the number of users with modified data", function()
|
||||
local store = Rodux.Store.new(CustomReducer, {
|
||||
Users = listOfUsers,
|
||||
}, {
|
||||
Rodux.thunkMiddleware,
|
||||
})
|
||||
|
||||
local currentUsers = store:getState().Users
|
||||
local listOfUsersWithPotentialUpdates = {
|
||||
Immutable.Set(currentUsers["2"], "presence", User.PresenceType.IN_GAME), -- changed
|
||||
Immutable.Set(currentUsers["5"], "isFriend", false), -- changed
|
||||
Immutable.Set(currentUsers["6"], "isFriend", true), -- did not change
|
||||
}
|
||||
|
||||
store:dispatch(UpdateUsers(listOfUsersWithPotentialUpdates))
|
||||
|
||||
local state = store:getState()
|
||||
expect(state.UsersReducerMonitor.numberOfAddUsersCalled).to.equal(1)
|
||||
expect(state.UsersReducerMonitor.numberOfUsersPassedIn).to.equal(2)
|
||||
end)
|
||||
|
||||
it("should correctly update the number of friends", function()
|
||||
local store = Rodux.Store.new(CustomReducer, {}, {
|
||||
Rodux.thunkMiddleware,
|
||||
})
|
||||
store:dispatch(UpdateUsers(listOfUsers))
|
||||
|
||||
local state = store:getState()
|
||||
expect(state.FriendCountReducerMonitor.numberOfSetFriendCountCalled).to.equal(1)
|
||||
expect(state.FriendCount).to.equal(5)
|
||||
|
||||
local currentUsers = store:getState().Users
|
||||
local listOfUsersWithPotentialUpdates = {
|
||||
Immutable.Set(currentUsers["2"], "presence", User.PresenceType.IN_GAME), -- friendship didn't change
|
||||
Immutable.Set(currentUsers["5"], "isFriend", false), -- friendship changed
|
||||
Immutable.Set(currentUsers["6"], "isFriend", false), -- friendship changed
|
||||
User.fromData(7, "Nibbler", true), -- new friend
|
||||
}
|
||||
|
||||
store:dispatch(UpdateUsers(listOfUsersWithPotentialUpdates))
|
||||
|
||||
state = store:getState()
|
||||
expect(state.FriendCount).to.equal(4)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Helper function to throttle based on player Id:
|
||||
return function(throttle, userId)
|
||||
assert(type(throttle) == "number")
|
||||
assert(type(userId) == "number")
|
||||
|
||||
-- Determine userRollout using last two digits of user ID:
|
||||
-- (+1 to change range from 0-99 to 1-100 as 0 is off, 100 is full on):
|
||||
local userRollout = (userId % 100) + 1
|
||||
return userRollout <= throttle
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
return function()
|
||||
local ThrottleUserId = require(script.Parent.ThrottleUserId)
|
||||
|
||||
describe("ThrottleUserId", function()
|
||||
it("should always reject zero%", function()
|
||||
local gating = ThrottleUserId(0, 10000)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(0, 10001)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(0, 10025)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(0, 10075)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(0, 10099)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(0, 10100)
|
||||
expect(gating).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should always accept 100%", function()
|
||||
local gating = ThrottleUserId(100, 10000)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(100, 10001)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(100, 10025)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(100, 10075)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(100, 10099)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(100, 10100)
|
||||
expect(gating).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should reject IDs over throttle percent", function()
|
||||
local gating = ThrottleUserId(25, 10050)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(50, 10075)
|
||||
expect(gating).to.equal(false)
|
||||
|
||||
gating = ThrottleUserId(75, 10099)
|
||||
expect(gating).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should accept IDs under throttle percent", function()
|
||||
local gating = ThrottleUserId(1, 10100)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(10, 10109)
|
||||
expect(gating).to.equal(true)
|
||||
|
||||
gating = ThrottleUserId(25, 10023)
|
||||
expect(gating).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user