@quenty/grouputils 10.22.0 → 10.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/deploy.nevermore.json +10 -0
- package/package.json +6 -3
- package/src/Shared/GroupTestUtils.lua +66 -0
- package/src/Shared/GroupUtils.lua +29 -11
- package/src/Shared/GroupUtils.spec.lua +214 -0
- package/src/jest.config.lua +3 -0
- package/test/default.project.json +17 -0
- package/test/scripts/Server/ServerMain.server.lua +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.23.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/grouputils@10.23.0...@quenty/grouputils@10.23.1) (2026-07-23)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/grouputils
|
|
9
|
+
|
|
10
|
+
# [10.23.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/grouputils@10.22.0...@quenty/grouputils@10.23.0) (2026-07-23)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- Add baseline player-mock and support across Nevermore for mocked players. ([567d121](https://github.com/Quenty/NevermoreEngine/commit/567d121ffc014b42391554088189a1a6296dda83))
|
|
15
|
+
|
|
6
16
|
# [10.22.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/grouputils@10.21.0...@quenty/grouputils@10.22.0) (2026-07-18)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @quenty/grouputils
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/grouputils",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.23.1",
|
|
4
4
|
"description": "Group utility functions for Roblox",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -30,10 +30,13 @@
|
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@quenty/loader": "10.11.0",
|
|
33
|
-
"@quenty/
|
|
33
|
+
"@quenty/nevermore-test-runner": "1.5.0",
|
|
34
|
+
"@quenty/playermock": "1.1.1",
|
|
35
|
+
"@quenty/promise": "10.21.0",
|
|
36
|
+
"@quentystudios/jest-lua": "3.10.0-quenty.2"
|
|
34
37
|
},
|
|
35
38
|
"publishConfig": {
|
|
36
39
|
"access": "public"
|
|
37
40
|
},
|
|
38
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "5ea0583dedebba6492f9ac6d719d32b45c5543fa"
|
|
39
42
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
Helpers for injecting group membership onto a [PlayerMock] in tests.
|
|
4
|
+
|
|
5
|
+
[GroupUtils] answers rank and role from two engine calls -- `GetRolesInGroupAsync`
|
|
6
|
+
(primary) and `GetGroupsAsync` (fallback) -- and only those calls are intercepted for
|
|
7
|
+
mocks, so its parsing and fallback logic runs for real in tests. [GroupTestUtils.assignGroupInfo]
|
|
8
|
+
writes both injected results from one rank/role pair so the answer is coherent whichever
|
|
9
|
+
path production code takes.
|
|
10
|
+
|
|
11
|
+
@class GroupTestUtils
|
|
12
|
+
]=]
|
|
13
|
+
|
|
14
|
+
local require = require(script.Parent.loader).load(script)
|
|
15
|
+
|
|
16
|
+
local PlayerMock = require("PlayerMock")
|
|
17
|
+
|
|
18
|
+
local GroupTestUtils = {}
|
|
19
|
+
|
|
20
|
+
export type AssignedGroupInfo = {
|
|
21
|
+
rank: number,
|
|
22
|
+
role: string,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
--[=[
|
|
26
|
+
Injects membership in a group for a mock at both engine calls [GroupUtils] reads, replacing
|
|
27
|
+
any earlier assignment for the same group. Assign multiple groups by calling once per group.
|
|
28
|
+
|
|
29
|
+
```lua
|
|
30
|
+
GroupTestUtils.assignGroupInfo(playerMock, 372, { rank = 230, role = "Admin" })
|
|
31
|
+
-- GroupUtils.promiseRankInGroup(playerMock, 372) resolves 230,
|
|
32
|
+
-- promiseRoleInGroup(playerMock, 372) resolves "Admin"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
@param player Player -- must be a PlayerMock
|
|
36
|
+
@param groupId number
|
|
37
|
+
@param groupInfo { rank: number, role: string }
|
|
38
|
+
]=]
|
|
39
|
+
function GroupTestUtils.assignGroupInfo(player: Player, groupId: number, groupInfo: AssignedGroupInfo)
|
|
40
|
+
assert(PlayerMock.isMock(player), "Not a PlayerMock")
|
|
41
|
+
assert(type(groupId) == "number", "Bad groupId")
|
|
42
|
+
assert(type(groupInfo) == "table", "Bad groupInfo")
|
|
43
|
+
assert(type(groupInfo.rank) == "number", "Bad groupInfo.rank")
|
|
44
|
+
assert(type(groupInfo.role) == "string", "Bad groupInfo.role")
|
|
45
|
+
|
|
46
|
+
PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", groupId, {
|
|
47
|
+
IsMember = true,
|
|
48
|
+
Roles = { { Name = groupInfo.role, Rank = groupInfo.rank } },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
local groups = PlayerMock.readLookup(player, "GroupService.GetGroupsAsync", 0)
|
|
52
|
+
local updated = {}
|
|
53
|
+
for _, existing in groups do
|
|
54
|
+
if existing.Id ~= groupId then
|
|
55
|
+
table.insert(updated, existing)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
table.insert(updated, {
|
|
59
|
+
Id = groupId,
|
|
60
|
+
Rank = groupInfo.rank,
|
|
61
|
+
Role = groupInfo.role,
|
|
62
|
+
})
|
|
63
|
+
PlayerMock.writeLookup(player, "GroupService.GetGroupsAsync", 0, updated)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return GroupTestUtils
|
|
@@ -9,6 +9,7 @@ local require = require(script.Parent.loader).load(script)
|
|
|
9
9
|
|
|
10
10
|
local GroupService = game:GetService("GroupService")
|
|
11
11
|
|
|
12
|
+
local PlayerMock = require("PlayerMock")
|
|
12
13
|
local Promise = require("Promise")
|
|
13
14
|
|
|
14
15
|
local GroupUtils = {}
|
|
@@ -48,17 +49,34 @@ type GetGroupDictionary = {
|
|
|
48
49
|
}
|
|
49
50
|
type GetGroupsAsyncResult = { GetGroupDictionary }
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
-- The mock interception point is the engine call itself (see [PlayerMock.writeLookup]), not the
|
|
53
|
+
-- promise wrappers below -- so the highest-role scan, fallback ordering, and reject paths all
|
|
54
|
+
-- run for mocks too. A PlayerMock is in no real Roblox group, so answering from the injected
|
|
55
|
+
-- result avoids the (flaky, fake-UserId) group lookup.
|
|
56
|
+
local function _getRolesInGroupAsync(player: Player, groupId: number): GetRolesInGroupAsyncResult
|
|
57
|
+
if PlayerMock.isMock(player) then
|
|
58
|
+
return PlayerMock.readLookup(player, "GroupService.GetRolesInGroupAsync", groupId)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return GroupService:GetRolesInGroupAsync(player.UserId, groupId) :: GetRolesInGroupAsyncResult
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local function _getRankAndRoleFallbackAsync(player: Player, groupId: number): (number, string?)
|
|
52
65
|
-- euvin: i yoinked this from
|
|
53
66
|
-- https://devforum.roblox.com/t/groupservicegetrolesingroupasync-is-not-enabled-yet-wiki-tells-me-to-use-it/4660969
|
|
54
67
|
|
|
55
68
|
--? The GetRankInGroup method is deprecated and unstable now.
|
|
56
69
|
--? https://devforum.roblox.com/t/excessive-rate-limits-when-checking-gamepasses-group-ranks/3549665
|
|
57
|
-
local groups
|
|
70
|
+
local groups: GetGroupsAsyncResult
|
|
71
|
+
if PlayerMock.isMock(player) then
|
|
72
|
+
groups = PlayerMock.readLookup(player, "GroupService.GetGroupsAsync", 0)
|
|
73
|
+
else
|
|
74
|
+
groups = GroupService:GetGroupsAsync(player.UserId) :: GetGroupsAsyncResult
|
|
75
|
+
end
|
|
58
76
|
|
|
59
|
-
for _,
|
|
60
|
-
if
|
|
61
|
-
return
|
|
77
|
+
for _, groupInfo: GetGroupDictionary in groups do
|
|
78
|
+
if groupInfo.Id == groupId then
|
|
79
|
+
return groupInfo.Rank, groupInfo.Role
|
|
62
80
|
end
|
|
63
81
|
end
|
|
64
82
|
|
|
@@ -73,7 +91,7 @@ end
|
|
|
73
91
|
@return Promise<number> -- Generally from 0 to 255
|
|
74
92
|
]=]
|
|
75
93
|
function GroupUtils.promiseRankInGroup(player: Player, groupId: number): Promise.Promise<number>
|
|
76
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
94
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
77
95
|
assert(type(groupId) == "number", "Bad groupId")
|
|
78
96
|
|
|
79
97
|
return Promise.spawn(function(resolve, reject)
|
|
@@ -82,7 +100,7 @@ function GroupUtils.promiseRankInGroup(player: Player, groupId: number): Promise
|
|
|
82
100
|
-- GetRankInGroupAsync is deprecated, changed from GetRankInGroupAsync to GetRolesInGroupAsync
|
|
83
101
|
-- ... but GetRolesInGroupAsync fails for some reason and hasn't been fixed (June 2, 2026)
|
|
84
102
|
-- so we will fall back to a deprecated method anyway
|
|
85
|
-
local result =
|
|
103
|
+
local result = _getRolesInGroupAsync(player, groupId)
|
|
86
104
|
if result.IsMember then
|
|
87
105
|
local highestRoleTable = _getHighestRoleTable(result.Roles)
|
|
88
106
|
if highestRoleTable then
|
|
@@ -92,7 +110,7 @@ function GroupUtils.promiseRankInGroup(player: Player, groupId: number): Promise
|
|
|
92
110
|
end)
|
|
93
111
|
if not rank then
|
|
94
112
|
ok, err = pcall(function()
|
|
95
|
-
local gotRank, _ =
|
|
113
|
+
local gotRank, _ = _getRankAndRoleFallbackAsync(player, groupId)
|
|
96
114
|
rank = gotRank
|
|
97
115
|
end)
|
|
98
116
|
end
|
|
@@ -117,13 +135,13 @@ end
|
|
|
117
135
|
@return Promise<string>
|
|
118
136
|
]=]
|
|
119
137
|
function GroupUtils.promiseRoleInGroup(player: Player, groupId: number): Promise.Promise<string>
|
|
120
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
138
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
121
139
|
assert(type(groupId) == "number", "Bad groupId")
|
|
122
140
|
|
|
123
141
|
return Promise.spawn(function(resolve, reject)
|
|
124
142
|
local role: string? = nil
|
|
125
143
|
local ok, err = pcall(function()
|
|
126
|
-
local result =
|
|
144
|
+
local result = _getRolesInGroupAsync(player, groupId)
|
|
127
145
|
if result.IsMember then
|
|
128
146
|
local highestRoleTable = _getHighestRoleTable(result.Roles)
|
|
129
147
|
if highestRoleTable then
|
|
@@ -134,7 +152,7 @@ function GroupUtils.promiseRoleInGroup(player: Player, groupId: number): Promise
|
|
|
134
152
|
|
|
135
153
|
if not role then
|
|
136
154
|
ok, err = pcall(function()
|
|
137
|
-
local _, gotRole =
|
|
155
|
+
local _, gotRole = _getRankAndRoleFallbackAsync(player, groupId)
|
|
138
156
|
role = gotRole
|
|
139
157
|
end)
|
|
140
158
|
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Coverage for GroupUtils rank/role resolution using PlayerMock players. Only the GroupService
|
|
4
|
+
engine calls are intercepted (raw result shapes injected via PlayerMock.writeLookup /
|
|
5
|
+
GroupTestUtils.assignGroupInfo), so the highest-role scan, GetGroupsAsync fallback ordering,
|
|
6
|
+
and reject paths all execute for real.
|
|
7
|
+
|
|
8
|
+
@class GroupUtils.spec.lua
|
|
9
|
+
]]
|
|
10
|
+
|
|
11
|
+
local require = require(script.Parent.loader).load(script)
|
|
12
|
+
|
|
13
|
+
local GroupTestUtils = require("GroupTestUtils")
|
|
14
|
+
local GroupUtils = require("GroupUtils")
|
|
15
|
+
local Jest = require("Jest")
|
|
16
|
+
local PlayerMock = require("PlayerMock")
|
|
17
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
18
|
+
|
|
19
|
+
local describe = Jest.Globals.describe
|
|
20
|
+
local expect = Jest.Globals.expect
|
|
21
|
+
local it = Jest.Globals.it
|
|
22
|
+
|
|
23
|
+
local GROUP_ID = 372
|
|
24
|
+
|
|
25
|
+
describe("GroupUtils.promiseRankInGroup", function()
|
|
26
|
+
it("should resolve 0 for a mock with no injected membership", function()
|
|
27
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
28
|
+
|
|
29
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
30
|
+
expect(outcome).toBe("resolved")
|
|
31
|
+
expect(value).toBe(0)
|
|
32
|
+
|
|
33
|
+
player:Destroy()
|
|
34
|
+
end)
|
|
35
|
+
|
|
36
|
+
it("should resolve an assigned rank", function()
|
|
37
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
38
|
+
GroupTestUtils.assignGroupInfo(player, GROUP_ID, { rank = 230, role = "Admin" })
|
|
39
|
+
|
|
40
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
41
|
+
expect(outcome).toBe("resolved")
|
|
42
|
+
expect(value).toBe(230)
|
|
43
|
+
|
|
44
|
+
player:Destroy()
|
|
45
|
+
end)
|
|
46
|
+
|
|
47
|
+
it("should resolve the highest rank when the result carries multiple roles", function()
|
|
48
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
49
|
+
PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", GROUP_ID, {
|
|
50
|
+
IsMember = true,
|
|
51
|
+
Roles = {
|
|
52
|
+
{ Name = "Member", Rank = 1 },
|
|
53
|
+
{ Name = "Admin", Rank = 230 },
|
|
54
|
+
{ Name = "Moderator", Rank = 150 },
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
59
|
+
expect(outcome).toBe("resolved")
|
|
60
|
+
expect(value).toBe(230)
|
|
61
|
+
|
|
62
|
+
player:Destroy()
|
|
63
|
+
end)
|
|
64
|
+
|
|
65
|
+
it("should resolve from the GetGroupsAsync fallback when the primary result reports non-member", function()
|
|
66
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
67
|
+
PlayerMock.writeLookup(player, "GroupService.GetGroupsAsync", 0, {
|
|
68
|
+
{ Id = GROUP_ID, Rank = 42, Role = "Member" },
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
72
|
+
expect(outcome).toBe("resolved")
|
|
73
|
+
expect(value).toBe(42)
|
|
74
|
+
|
|
75
|
+
player:Destroy()
|
|
76
|
+
end)
|
|
77
|
+
|
|
78
|
+
it("should ignore membership assigned for a different group", function()
|
|
79
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
80
|
+
GroupTestUtils.assignGroupInfo(player, 99999, { rank = 255, role = "Owner" })
|
|
81
|
+
|
|
82
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
83
|
+
expect(outcome).toBe("resolved")
|
|
84
|
+
expect(value).toBe(0)
|
|
85
|
+
|
|
86
|
+
player:Destroy()
|
|
87
|
+
end)
|
|
88
|
+
|
|
89
|
+
it("should throw on a non-player value", function()
|
|
90
|
+
expect(function()
|
|
91
|
+
GroupUtils.promiseRankInGroup(nil :: any, GROUP_ID)
|
|
92
|
+
end).toThrow("Bad player")
|
|
93
|
+
end)
|
|
94
|
+
|
|
95
|
+
it("should throw on a bad groupId", function()
|
|
96
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
97
|
+
|
|
98
|
+
expect(function()
|
|
99
|
+
GroupUtils.promiseRankInGroup(player, "372" :: any)
|
|
100
|
+
end).toThrow("Bad groupId")
|
|
101
|
+
|
|
102
|
+
player:Destroy()
|
|
103
|
+
end)
|
|
104
|
+
end)
|
|
105
|
+
|
|
106
|
+
describe("GroupUtils.promiseRoleInGroup", function()
|
|
107
|
+
it("should resolve an assigned role", function()
|
|
108
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
109
|
+
GroupTestUtils.assignGroupInfo(player, GROUP_ID, { rank = 230, role = "Admin" })
|
|
110
|
+
|
|
111
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRoleInGroup(player, GROUP_ID))
|
|
112
|
+
expect(outcome).toBe("resolved")
|
|
113
|
+
expect(value).toBe("Admin")
|
|
114
|
+
|
|
115
|
+
player:Destroy()
|
|
116
|
+
end)
|
|
117
|
+
|
|
118
|
+
it("should resolve the highest role's name when the result carries multiple roles", function()
|
|
119
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
120
|
+
PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", GROUP_ID, {
|
|
121
|
+
IsMember = true,
|
|
122
|
+
Roles = {
|
|
123
|
+
{ Name = "Member", Rank = 1 },
|
|
124
|
+
{ Name = "Admin", Rank = 230 },
|
|
125
|
+
{ Name = "Moderator", Rank = 150 },
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRoleInGroup(player, GROUP_ID))
|
|
130
|
+
expect(outcome).toBe("resolved")
|
|
131
|
+
expect(value).toBe("Admin")
|
|
132
|
+
|
|
133
|
+
player:Destroy()
|
|
134
|
+
end)
|
|
135
|
+
|
|
136
|
+
it("should resolve from the GetGroupsAsync fallback when the primary result reports non-member", function()
|
|
137
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
138
|
+
PlayerMock.writeLookup(player, "GroupService.GetGroupsAsync", 0, {
|
|
139
|
+
{ Id = GROUP_ID, Rank = 42, Role = "Member" },
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
local outcome, value = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRoleInGroup(player, GROUP_ID))
|
|
143
|
+
expect(outcome).toBe("resolved")
|
|
144
|
+
expect(value).toBe("Member")
|
|
145
|
+
|
|
146
|
+
player:Destroy()
|
|
147
|
+
end)
|
|
148
|
+
|
|
149
|
+
it("should reject for a mock with no injected membership, like a real non-member", function()
|
|
150
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
151
|
+
|
|
152
|
+
local outcome, err = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRoleInGroup(player, GROUP_ID))
|
|
153
|
+
expect(outcome).toBe("rejected")
|
|
154
|
+
expect(err).toBe("Role is not a string")
|
|
155
|
+
|
|
156
|
+
player:Destroy()
|
|
157
|
+
end)
|
|
158
|
+
end)
|
|
159
|
+
|
|
160
|
+
describe("GroupTestUtils.assignGroupInfo", function()
|
|
161
|
+
it("should keep rank and role coherent from one assignment", function()
|
|
162
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
163
|
+
GroupTestUtils.assignGroupInfo(player, GROUP_ID, { rank = 150, role = "Moderator" })
|
|
164
|
+
|
|
165
|
+
local _, rank = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
166
|
+
local _, role = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRoleInGroup(player, GROUP_ID))
|
|
167
|
+
expect(rank).toBe(150)
|
|
168
|
+
expect(role).toBe("Moderator")
|
|
169
|
+
|
|
170
|
+
player:Destroy()
|
|
171
|
+
end)
|
|
172
|
+
|
|
173
|
+
it("should keep assignments to multiple groups independent", function()
|
|
174
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
175
|
+
GroupTestUtils.assignGroupInfo(player, GROUP_ID, { rank = 230, role = "Admin" })
|
|
176
|
+
GroupTestUtils.assignGroupInfo(player, 99999, { rank = 5, role = "Member" })
|
|
177
|
+
|
|
178
|
+
-- Clearing one group's primary result forces its answer through the shared GetGroupsAsync
|
|
179
|
+
-- fallback list, proving the second assignment appended rather than replaced it.
|
|
180
|
+
PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", GROUP_ID, nil)
|
|
181
|
+
|
|
182
|
+
local _, rank = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
183
|
+
local _, otherRank = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, 99999))
|
|
184
|
+
expect(rank).toBe(230)
|
|
185
|
+
expect(otherRank).toBe(5)
|
|
186
|
+
|
|
187
|
+
player:Destroy()
|
|
188
|
+
end)
|
|
189
|
+
|
|
190
|
+
it("should replace an earlier assignment for the same group", function()
|
|
191
|
+
local player = PlayerMock.new({ UserId = 111 })
|
|
192
|
+
GroupTestUtils.assignGroupInfo(player, GROUP_ID, { rank = 10, role = "Member" })
|
|
193
|
+
GroupTestUtils.assignGroupInfo(player, GROUP_ID, { rank = 20, role = "Moderator" })
|
|
194
|
+
|
|
195
|
+
local _, rank = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
196
|
+
expect(rank).toBe(20)
|
|
197
|
+
|
|
198
|
+
PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", GROUP_ID, nil)
|
|
199
|
+
local _, fallbackRank = PromiseTestUtils.awaitOutcome(GroupUtils.promiseRankInGroup(player, GROUP_ID))
|
|
200
|
+
expect(fallbackRank).toBe(20)
|
|
201
|
+
|
|
202
|
+
player:Destroy()
|
|
203
|
+
end)
|
|
204
|
+
|
|
205
|
+
it("should throw when passed something that is not a PlayerMock", function()
|
|
206
|
+
local folder = Instance.new("Folder")
|
|
207
|
+
|
|
208
|
+
expect(function()
|
|
209
|
+
GroupTestUtils.assignGroupInfo(folder :: any, GROUP_ID, { rank = 1, role = "Member" })
|
|
210
|
+
end).toThrow("Not a PlayerMock")
|
|
211
|
+
|
|
212
|
+
folder:Destroy()
|
|
213
|
+
end)
|
|
214
|
+
end)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "GroupUtilsTest",
|
|
3
|
+
"tree": {
|
|
4
|
+
"$className": "DataModel",
|
|
5
|
+
"ServerScriptService": {
|
|
6
|
+
"$properties": {
|
|
7
|
+
"LoadStringEnabled": true
|
|
8
|
+
},
|
|
9
|
+
"grouputils": {
|
|
10
|
+
"$path": ".."
|
|
11
|
+
},
|
|
12
|
+
"Script": {
|
|
13
|
+
"$path": "scripts/Server"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
--!nonstrict
|
|
2
|
+
local ServerScriptService = game:GetService("ServerScriptService")
|
|
3
|
+
|
|
4
|
+
local root = ServerScriptService.grouputils
|
|
5
|
+
local loader = root:FindFirstChild("LoaderUtils", true).Parent
|
|
6
|
+
local require = require(loader).bootstrapGame(root)
|
|
7
|
+
|
|
8
|
+
local NevermoreTestRunnerUtils = require("NevermoreTestRunnerUtils")
|
|
9
|
+
|
|
10
|
+
if NevermoreTestRunnerUtils.runTestsIfNeededAsync(root) then
|
|
11
|
+
return
|
|
12
|
+
end
|