@quenty/pagesutils 5.21.0 → 5.22.0

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 CHANGED
@@ -3,6 +3,12 @@
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
+ # [5.22.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/pagesutils@5.21.0...@quenty/pagesutils@5.22.0) (2026-07-23)
7
+
8
+ ### Features
9
+
10
+ - Add baseline player-mock and support across Nevermore for mocked players. ([567d121](https://github.com/Quenty/NevermoreEngine/commit/567d121ffc014b42391554088189a1a6296dda83))
11
+
6
12
  # [5.21.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/pagesutils@5.20.0...@quenty/pagesutils@5.21.0) (2026-07-18)
7
13
 
8
14
  **Note:** Version bump only for package @quenty/pagesutils
@@ -0,0 +1,10 @@
1
+ {
2
+ "targets": {
3
+ "test": {
4
+ "universeId": 9716264427,
5
+ "placeId": 117954086576258,
6
+ "project": "test/default.project.json",
7
+ "scriptTemplate": "test/scripts/Server/ServerMain.server.lua"
8
+ }
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/pagesutils",
3
- "version": "5.21.0",
3
+ "version": "5.22.0",
4
4
  "description": "Utilities to advance over the Roblox pages API surface",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -31,10 +31,12 @@
31
31
  "dependencies": {
32
32
  "@quenty/ducktype": "5.11.0",
33
33
  "@quenty/loader": "10.11.0",
34
- "@quenty/promise": "10.20.0"
34
+ "@quenty/nevermore-test-runner": "1.5.0",
35
+ "@quenty/promise": "10.21.0",
36
+ "@quentystudios/jest-lua": "3.10.0-quenty.2"
35
37
  },
36
38
  "publishConfig": {
37
39
  "access": "public"
38
40
  },
39
- "gitHead": "647acfad93dca41c38f59cda247d2bfc0fff36d4"
41
+ "gitHead": "1de37218a2bedb8e3f8614a2e09bba9eddc812da"
40
42
  }
@@ -13,7 +13,8 @@ PagesDatabase.__index = PagesDatabase
13
13
 
14
14
  export type PagesDatabase = typeof(setmetatable(
15
15
  {} :: {
16
- _pages: Pages,
16
+ -- nil for a static database (see [PagesDatabase.fromPageData]), which has every page pre-stored
17
+ _pages: Pages?,
17
18
  _lastIncrementedIndex: number,
18
19
  _pageData: { [number]: { currentPage: any, isFinished: boolean } },
19
20
  },
@@ -32,14 +33,56 @@ function PagesDatabase.new(pages: Pages): PagesDatabase
32
33
  return self
33
34
  end
34
35
 
36
+ --[=[
37
+ Constructs a database from static page data instead of a live [Pages] instance, for consumers
38
+ that already hold the full result set -- e.g. a test fabricating an engine pages result through
39
+ [PagesProxy]. Each entry in `pageData` is one page's item array; the last page reads back
40
+ `IsFinished = true`. Empty `pageData` mirrors an empty engine result: a single empty page that
41
+ is already finished.
42
+
43
+ ```lua
44
+ local pages = PagesProxy.new(PagesDatabase.fromPageData({
45
+ { "a", "b" },
46
+ { "c" },
47
+ }))
48
+ ```
49
+
50
+ @param pageData { { any } }
51
+ @return PagesDatabase
52
+ ]=]
53
+ function PagesDatabase.fromPageData(pageData: { { any } }): PagesDatabase
54
+ assert(type(pageData) == "table", "Bad pageData")
55
+
56
+ local self: PagesDatabase = setmetatable({} :: any, PagesDatabase)
57
+
58
+ local pageCount = math.max(#pageData, 1)
59
+
60
+ self._lastIncrementedIndex = pageCount
61
+ self._pageData = {}
62
+
63
+ for pageId = 1, pageCount do
64
+ local page = pageData[pageId] or {}
65
+ assert(type(page) == "table", "Bad page")
66
+
67
+ self._pageData[pageId] = {
68
+ currentPage = page,
69
+ isFinished = pageId == pageCount,
70
+ }
71
+ end
72
+
73
+ return self
74
+ end
75
+
35
76
  function PagesDatabase.isPagesDatabase(value): boolean
36
77
  return DuckTypeUtils.isImplementation(PagesDatabase, value)
37
78
  end
38
79
 
39
80
  function PagesDatabase.IncrementToPageIdAsync(self: PagesDatabase, pageId: number)
40
81
  while self._lastIncrementedIndex < pageId do
82
+ local pages = assert(self._pages, "Cannot advance a static PagesDatabase past its stored pages")
83
+
41
84
  self._lastIncrementedIndex += 1
42
- self._pages:AdvanceToNextPageAsync()
85
+ pages:AdvanceToNextPageAsync()
43
86
  self:_storeState()
44
87
  end
45
88
  end
@@ -63,9 +106,11 @@ function PagesDatabase._getPageState(self: PagesDatabase, pageId: number)
63
106
  end
64
107
 
65
108
  function PagesDatabase._storeState(self: PagesDatabase)
109
+ local pages = assert(self._pages, "No pages")
110
+
66
111
  self._pageData[self._lastIncrementedIndex] = {
67
- currentPage = self._pages:GetCurrentPage(),
68
- isFinished = self._pages.IsFinished,
112
+ currentPage = pages:GetCurrentPage(),
113
+ isFinished = pages.IsFinished,
69
114
  }
70
115
  end
71
116
 
@@ -0,0 +1,106 @@
1
+ --!strict
2
+ --[[
3
+ @class PagesDatabase.spec.lua
4
+ ]]
5
+
6
+ local require = require(script.Parent.loader).load(script)
7
+
8
+ local Jest = require("Jest")
9
+ local PagesDatabase = require("PagesDatabase")
10
+ local PagesProxy = require("PagesProxy")
11
+ local PagesUtils = require("PagesUtils")
12
+ local PromiseTestUtils = require("PromiseTestUtils")
13
+
14
+ local describe = Jest.Globals.describe
15
+ local expect = Jest.Globals.expect
16
+ local it = Jest.Globals.it
17
+
18
+ describe("PagesDatabase.fromPageData", function()
19
+ it("should store each page with the last one finished", function()
20
+ local database = PagesDatabase.fromPageData({
21
+ { "a", "b" },
22
+ { "c" },
23
+ })
24
+
25
+ expect(PagesDatabase.isPagesDatabase(database)).toBe(true)
26
+ expect(database:GetPage(1)).toEqual({ "a", "b" })
27
+ expect(database:GetIsFinished(1)).toBe(false)
28
+ expect(database:GetPage(2)).toEqual({ "c" })
29
+ expect(database:GetIsFinished(2)).toBe(true)
30
+ end)
31
+
32
+ it("should mirror an empty engine result as one finished empty page", function()
33
+ local database = PagesDatabase.fromPageData({})
34
+
35
+ expect(database:GetPage(1)).toEqual({})
36
+ expect(database:GetIsFinished(1)).toBe(true)
37
+ end)
38
+
39
+ it("should no-op advancement to already-stored pages", function()
40
+ local database = PagesDatabase.fromPageData({ { "a" }, { "b" } })
41
+
42
+ database:IncrementToPageIdAsync(2)
43
+
44
+ expect(database:GetPage(2)).toEqual({ "b" })
45
+ end)
46
+
47
+ it("should error on advancement past the stored pages", function()
48
+ local database = PagesDatabase.fromPageData({ { "a" } })
49
+
50
+ expect(function()
51
+ database:IncrementToPageIdAsync(2)
52
+ end).toThrow()
53
+ end)
54
+ end)
55
+
56
+ describe("PagesProxy over a static database", function()
57
+ it("should duck-type as a pages instance and iterate every page", function()
58
+ local proxy = PagesProxy.new(PagesDatabase.fromPageData({
59
+ { "a", "b" },
60
+ { "c" },
61
+ }))
62
+
63
+ expect(PagesProxy.isPagesProxy(proxy)).toBe(true)
64
+ expect(proxy:GetCurrentPage()).toEqual({ "a", "b" })
65
+ expect((proxy :: any).IsFinished).toBe(false)
66
+
67
+ proxy:AdvanceToNextPageAsync()
68
+
69
+ expect(proxy:GetCurrentPage()).toEqual({ "c" })
70
+ expect((proxy :: any).IsFinished).toBe(true)
71
+ expect(function()
72
+ proxy:AdvanceToNextPageAsync()
73
+ end).toThrow()
74
+ end)
75
+
76
+ it("should clone without sharing advancement state", function()
77
+ local proxy = PagesProxy.new(PagesDatabase.fromPageData({ { "a" }, { "b" } }))
78
+ local clone = proxy:Clone()
79
+
80
+ clone:AdvanceToNextPageAsync()
81
+
82
+ expect(proxy:GetCurrentPage()).toEqual({ "a" })
83
+ expect(clone:GetCurrentPage()).toEqual({ "b" })
84
+ end)
85
+ end)
86
+
87
+ describe("PagesUtils.promiseAdvanceToNextPage", function()
88
+ it("should resolve the next page's content for a proxy", function()
89
+ local proxy = PagesProxy.new(PagesDatabase.fromPageData({ { "a" }, { "b" } }))
90
+
91
+ local promise = PagesUtils.promiseAdvanceToNextPage(proxy :: any)
92
+ local outcome, value = PromiseTestUtils.awaitOutcome(promise)
93
+
94
+ expect(outcome).toBe("resolved")
95
+ expect(value).toEqual({ "b" })
96
+ end)
97
+
98
+ it("should reject when the proxy is already finished", function()
99
+ local proxy = PagesProxy.new(PagesDatabase.fromPageData({ { "a" } }))
100
+
101
+ local promise = PagesUtils.promiseAdvanceToNextPage(proxy :: any)
102
+ local outcome = PromiseTestUtils.awaitOutcome(promise)
103
+
104
+ expect(outcome).toBe("rejected")
105
+ end)
106
+ end)
@@ -0,0 +1,3 @@
1
+ return {
2
+ testMatch = { "**/*.spec" },
3
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "PagesUtilsTest",
3
+ "tree": {
4
+ "$className": "DataModel",
5
+ "ServerScriptService": {
6
+ "$properties": {
7
+ "LoadStringEnabled": true
8
+ },
9
+ "pagesutils": {
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.pagesutils
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