@platonic-dice/dice 2.0.2 → 2.0.3
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/dist/{src/components → components}/die-history/history-cache.js +1 -1
- package/dist/components/die-history/index.js +2 -0
- package/dist/components/die-history/internal/index.js +2 -0
- package/dist/{src/components → components}/die-history/internal/roll-record-manager/index.js +1 -1
- package/dist/components/die-history/internal/roll-record-manager/internal/index.js +1 -0
- package/dist/{src/components → components}/die-history/internal/roll-record-manager/roll-record-manager.js +2 -2
- package/dist/{src/components → components}/die-history/internal/roll-record-validator.js +2 -1
- package/dist/{src/components → components}/die-history/roll-record-factory.js +3 -2
- package/dist/components/index.js +1 -0
- package/dist/{src/die.js → die.js} +3 -2
- package/dist/index.js +11 -0
- package/dist/types/index.js +1 -0
- package/package.json +4 -3
- package/dist/__tests__/components/die-history/history-cache.spec.js +0 -106
- package/dist/__tests__/components/die-history/internal/roll-record-manager/internal/roll-record-storage.spec.js +0 -40
- package/dist/__tests__/components/die-history/internal/roll-record-manager/roll-record-manager.spec.js +0 -112
- package/dist/__tests__/components/die-history/internal/roll-record-validator.spec.js +0 -38
- package/dist/__tests__/components/die-history/roll-record-factory.spec.js +0 -43
- package/dist/__tests__/die.spec.js +0 -140
- package/dist/__tests__/types/roll-record.types.spec.js +0 -41
- package/dist/src/components/die-history/index.js +0 -2
- package/dist/src/components/die-history/internal/index.js +0 -2
- package/dist/src/components/die-history/internal/roll-record-manager/internal/index.js +0 -1
- package/dist/src/components/index.js +0 -1
- package/dist/src/types/index.js +0 -1
- /package/dist/{src/components → components}/die-history/internal/roll-record-manager/internal/roll-record-storage.js +0 -0
- /package/dist/{src/types → types}/roll-record.types.js +0 -0
package/dist/{src/components → components}/die-history/internal/roll-record-manager/index.js
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { RollRecordManager, DEFAULT_MAX_RECORDS } from "./roll-record-manager";
|
|
1
|
+
export { RollRecordManager, DEFAULT_MAX_RECORDS } from "./roll-record-manager.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./roll-record-storage.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isDieRollRecord, isModifiedDieRollRecord, isTargetDieRollRecord, stripTimestamp, } from "../roll-record-validator";
|
|
2
|
-
import { RollRecordStorage } from "./internal";
|
|
1
|
+
import { isDieRollRecord, isModifiedDieRollRecord, isTargetDieRollRecord, stripTimestamp, } from "../roll-record-validator.js";
|
|
2
|
+
import { RollRecordStorage } from "./internal/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Default maximum number of roll records stored.
|
|
5
5
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import core from "@platonic-dice/core";
|
|
2
|
+
const { RollType, roll: coreRoll, rollMod: coreRollMod, rollTest: coreRollTest, } = core;
|
|
3
|
+
import { isDieRollRecord, isModifiedDieRollRecord, isTargetDieRollRecord, } from "./internal/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Default implementation that delegates to `@platonic-dice/core` and uses
|
|
5
6
|
* the system clock. This keeps the public API simple and avoids DI.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./die-history/index.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import core from "@platonic-dice/core";
|
|
2
|
+
const { DieType, RollType } = core;
|
|
3
|
+
import { RollRecordFactory, HistoryCache } from "./components/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Represents a single die with flexible history tracking.
|
|
5
6
|
*
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @platonic-dice/dice
|
|
3
|
+
* @description
|
|
4
|
+
* Persistent dice objects with roll history and TypeScript support.
|
|
5
|
+
*/
|
|
6
|
+
// Main Die class
|
|
7
|
+
export { Die } from "./die.js";
|
|
8
|
+
// Re-export core types and values that consumers will need
|
|
9
|
+
// Note: Import from default due to CommonJS/ESM interop
|
|
10
|
+
import core from "@platonic-dice/core";
|
|
11
|
+
export const { DieType, RollType } = core;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./roll-record.types.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platonic-dice/dice",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Persistent dice objects with roll history and TypeScript support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://github.com/sjs2k20/platonic-dice.git#readme",
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsc -p tsconfig.json",
|
|
36
|
+
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
37
37
|
"prepublishOnly": "npm run build",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"test:watch": "vitest"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@platonic-dice/core": "^2.0.
|
|
42
|
+
"@platonic-dice/core": "^2.0.3"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"tsc-alias": "^1.8.16",
|
|
45
46
|
"vite-tsconfig-paths": "^5.1.4",
|
|
46
47
|
"vitest": "^4.0.9"
|
|
47
48
|
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/// <reference types="vitest" />
|
|
2
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
3
|
-
import { HistoryCache } from "@dice/components/die-history";
|
|
4
|
-
describe("RollHistoryCache", () => {
|
|
5
|
-
let cache;
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
cache = new HistoryCache({
|
|
8
|
-
maxRecordsPerKey: 3,
|
|
9
|
-
maxKeys: 2,
|
|
10
|
-
});
|
|
11
|
-
});
|
|
12
|
-
it("starts empty", () => {
|
|
13
|
-
expect(cache.activeManager).toBeUndefined();
|
|
14
|
-
expect(cache.getAll()).toEqual([]);
|
|
15
|
-
});
|
|
16
|
-
it("throws if add called before setting active key", () => {
|
|
17
|
-
expect(() => cache.add({ roll: 1, timestamp: new Date() })).toThrow(/No active history key set/);
|
|
18
|
-
});
|
|
19
|
-
it("creates and sets an active key", () => {
|
|
20
|
-
cache.setActiveKey("first");
|
|
21
|
-
expect(cache.activeManager).toBeDefined();
|
|
22
|
-
expect(cache.getAll()).toEqual([]);
|
|
23
|
-
});
|
|
24
|
-
it("adds records to the active key", () => {
|
|
25
|
-
cache.setActiveKey("key1");
|
|
26
|
-
const record = { roll: 5, timestamp: new Date() };
|
|
27
|
-
cache.add(record);
|
|
28
|
-
const all = cache.getAll(true);
|
|
29
|
-
expect(all.length).toBe(1);
|
|
30
|
-
expect(all[0].roll).toBe(5);
|
|
31
|
-
});
|
|
32
|
-
it("respects maxRecordsPerKey", () => {
|
|
33
|
-
cache.setActiveKey("key1");
|
|
34
|
-
const now = new Date();
|
|
35
|
-
for (let i = 1; i <= 5; i++) {
|
|
36
|
-
cache.add({ roll: i, timestamp: new Date(now.getTime() + i) });
|
|
37
|
-
}
|
|
38
|
-
const all = cache.getAll(true);
|
|
39
|
-
expect(all.length).toBe(3); // capped
|
|
40
|
-
expect(all[0].roll).toBe(3); // oldest removed
|
|
41
|
-
expect(all[2].roll).toBe(5);
|
|
42
|
-
});
|
|
43
|
-
it("switching keys creates independent histories", () => {
|
|
44
|
-
cache.setActiveKey("A");
|
|
45
|
-
cache.add({ roll: 1, timestamp: new Date() });
|
|
46
|
-
cache.setActiveKey("B");
|
|
47
|
-
cache.add({ roll: 2, timestamp: new Date() });
|
|
48
|
-
cache.setActiveKey("A");
|
|
49
|
-
expect(cache.getAll(true)[0].roll).toBe(1);
|
|
50
|
-
cache.setActiveKey("B");
|
|
51
|
-
expect(cache.getAll(true)[0].roll).toBe(2);
|
|
52
|
-
});
|
|
53
|
-
it("evicts oldest key when maxKeys exceeded", () => {
|
|
54
|
-
cache.setActiveKey("K1");
|
|
55
|
-
cache.add({ roll: 1, timestamp: new Date() });
|
|
56
|
-
cache.setActiveKey("K2");
|
|
57
|
-
cache.add({ roll: 2, timestamp: new Date() });
|
|
58
|
-
// Max keys = 2, next key should evict K1
|
|
59
|
-
cache.setActiveKey("K3");
|
|
60
|
-
cache.add({ roll: 3, timestamp: new Date() });
|
|
61
|
-
const report = cache.report({ verbose: true });
|
|
62
|
-
expect(Object.keys(report)).not.toContain("K1");
|
|
63
|
-
expect(Object.keys(report)).toContain("K2");
|
|
64
|
-
expect(Object.keys(report)).toContain("K3");
|
|
65
|
-
});
|
|
66
|
-
it("clearActive clears only the active key", () => {
|
|
67
|
-
cache.setActiveKey("A");
|
|
68
|
-
cache.add({ roll: 1, timestamp: new Date() });
|
|
69
|
-
cache.setActiveKey("B");
|
|
70
|
-
cache.add({ roll: 2, timestamp: new Date() });
|
|
71
|
-
cache.setActiveKey("A");
|
|
72
|
-
cache.clearActive();
|
|
73
|
-
expect(cache.getAll(true)).toEqual([]);
|
|
74
|
-
cache.setActiveKey("B");
|
|
75
|
-
expect(cache.getAll(true).length).toBe(1);
|
|
76
|
-
});
|
|
77
|
-
it("clearAll clears everything", () => {
|
|
78
|
-
cache.setActiveKey("X");
|
|
79
|
-
cache.add({ roll: 1, timestamp: new Date() });
|
|
80
|
-
cache.setActiveKey("Y");
|
|
81
|
-
cache.add({ roll: 2, timestamp: new Date() });
|
|
82
|
-
cache.clearAll();
|
|
83
|
-
expect(cache.getAll()).toEqual([]);
|
|
84
|
-
expect(Object.keys(cache.report())).toEqual([]);
|
|
85
|
-
});
|
|
86
|
-
it("report returns correct verbose and non-verbose data", () => {
|
|
87
|
-
cache.setActiveKey("R1");
|
|
88
|
-
cache.add({ roll: 10, timestamp: new Date() });
|
|
89
|
-
const reportsVerbose = cache.report({ verbose: true });
|
|
90
|
-
const reportsNonVerbose = cache.report({ verbose: false });
|
|
91
|
-
expect(reportsVerbose["R1"][0]).toHaveProperty("timestamp");
|
|
92
|
-
expect(reportsNonVerbose["R1"][0]).not.toHaveProperty("timestamp");
|
|
93
|
-
});
|
|
94
|
-
it("toString returns a readable summary", () => {
|
|
95
|
-
cache.setActiveKey("Active");
|
|
96
|
-
expect(cache.toString()).toMatch(/HistoryCache: 1 keys \(active: Active\)/);
|
|
97
|
-
});
|
|
98
|
-
it("toJSON returns object with arrays of RollRecords", () => {
|
|
99
|
-
cache.setActiveKey("J");
|
|
100
|
-
const record = { roll: 7, timestamp: new Date() };
|
|
101
|
-
cache.add(record);
|
|
102
|
-
const json = cache.toJSON();
|
|
103
|
-
expect(json).toHaveProperty("J");
|
|
104
|
-
expect(json["J"][0].roll).toBe(7);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import RollRecordStorage from "@dice/components/die-history/internal/roll-record-manager/internal/roll-record-storage";
|
|
3
|
-
describe("RollRecordStorage", () => {
|
|
4
|
-
it("evicts oldest when capacity exceeded", () => {
|
|
5
|
-
const s = new RollRecordStorage(2);
|
|
6
|
-
s.add({ roll: 1, timestamp: new Date() });
|
|
7
|
-
s.add({ roll: 2, timestamp: new Date() });
|
|
8
|
-
s.add({ roll: 3, timestamp: new Date() });
|
|
9
|
-
expect(s.size).toBe(2);
|
|
10
|
-
expect(s.full.map((r) => r.roll)).toEqual([2, 3]);
|
|
11
|
-
});
|
|
12
|
-
it("last returns correct slice and order", () => {
|
|
13
|
-
const s = new RollRecordStorage(10);
|
|
14
|
-
s.add({ roll: 1, timestamp: new Date() });
|
|
15
|
-
s.add({ roll: 2, timestamp: new Date() });
|
|
16
|
-
s.add({ roll: 3, timestamp: new Date() });
|
|
17
|
-
s.add({ roll: 4, timestamp: new Date() });
|
|
18
|
-
const last2 = s.last(2);
|
|
19
|
-
expect(last2.map((r) => r.roll)).toEqual([3, 4]);
|
|
20
|
-
});
|
|
21
|
-
it("getters return array copies (mutating returned arrays doesn't affect storage)", () => {
|
|
22
|
-
const s = new RollRecordStorage(3);
|
|
23
|
-
s.add({ roll: 1, timestamp: new Date() });
|
|
24
|
-
const full = s.full;
|
|
25
|
-
full.pop();
|
|
26
|
-
expect(s.size).toBe(1);
|
|
27
|
-
const last = s.last(1);
|
|
28
|
-
// mutating the returned array should not affect internal storage length
|
|
29
|
-
last.pop();
|
|
30
|
-
expect(s.size).toBe(1);
|
|
31
|
-
});
|
|
32
|
-
it("throws for invalid constructor args and parameters", () => {
|
|
33
|
-
// call via any to test runtime throw without TS compile errors
|
|
34
|
-
expect(() => new RollRecordStorage(0)).toThrow(TypeError);
|
|
35
|
-
const s = new RollRecordStorage(2);
|
|
36
|
-
expect(() => s.last(0)).toThrow(TypeError);
|
|
37
|
-
// pass null to add
|
|
38
|
-
expect(() => s.add(null)).toThrow(TypeError);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { Outcome } from "@platonic-dice/core";
|
|
3
|
-
import { DEFAULT_MAX_RECORDS, RollRecordManager, } from "@dice/components/die-history/internal";
|
|
4
|
-
describe("RollRecordManager", () => {
|
|
5
|
-
let manager;
|
|
6
|
-
const dieRoll = {
|
|
7
|
-
roll: 5,
|
|
8
|
-
timestamp: new Date(),
|
|
9
|
-
};
|
|
10
|
-
const modifiedDieRoll = {
|
|
11
|
-
roll: 3,
|
|
12
|
-
modified: 1,
|
|
13
|
-
timestamp: new Date(),
|
|
14
|
-
};
|
|
15
|
-
const targetDieRoll = {
|
|
16
|
-
roll: 20,
|
|
17
|
-
outcome: Outcome.Success,
|
|
18
|
-
timestamp: new Date(),
|
|
19
|
-
};
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
manager = new RollRecordManager();
|
|
22
|
-
});
|
|
23
|
-
it("should initialize empty with default maxRecords", () => {
|
|
24
|
-
expect(manager.length).toBe(0);
|
|
25
|
-
expect(manager.maxRecordsCount).toBe(DEFAULT_MAX_RECORDS);
|
|
26
|
-
expect(manager.full).toEqual([]);
|
|
27
|
-
expect(manager.all).toEqual([]);
|
|
28
|
-
});
|
|
29
|
-
it("should add DieRollRecord correctly", () => {
|
|
30
|
-
manager.add(dieRoll);
|
|
31
|
-
expect(manager.length).toBe(1);
|
|
32
|
-
expect(manager.full[0]).toEqual(dieRoll);
|
|
33
|
-
expect(manager.all[0]).toEqual({ roll: dieRoll.roll });
|
|
34
|
-
});
|
|
35
|
-
it("should add ModifiedDieRollRecord correctly", () => {
|
|
36
|
-
manager.add(modifiedDieRoll);
|
|
37
|
-
expect(manager.length).toBe(1);
|
|
38
|
-
expect(manager.full[0]).toEqual(modifiedDieRoll);
|
|
39
|
-
expect(manager.all[0]).toEqual({
|
|
40
|
-
roll: modifiedDieRoll.roll,
|
|
41
|
-
modified: modifiedDieRoll.modified,
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
it("should add TargetDieRollRecord correctly", () => {
|
|
45
|
-
manager.add(targetDieRoll);
|
|
46
|
-
expect(manager.length).toBe(1);
|
|
47
|
-
expect(manager.full[0]).toEqual(targetDieRoll);
|
|
48
|
-
expect(manager.all[0]).toEqual({
|
|
49
|
-
roll: targetDieRoll.roll,
|
|
50
|
-
outcome: targetDieRoll.outcome,
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
it("should throw TypeError for invalid records", () => {
|
|
54
|
-
// @ts-expect-error testing runtime validation
|
|
55
|
-
expect(() => manager.add({})).toThrow(TypeError);
|
|
56
|
-
// @ts-expect-error testing runtime validation
|
|
57
|
-
expect(() => manager.add(null)).toThrow(TypeError);
|
|
58
|
-
// @ts-expect-error testing runtime validation
|
|
59
|
-
expect(() => manager.add({ roll: 5, foo: "bar" })).toThrow(TypeError);
|
|
60
|
-
});
|
|
61
|
-
it("should maintain maxRecords correctly", () => {
|
|
62
|
-
const smallManager = new RollRecordManager(2);
|
|
63
|
-
smallManager.add({ roll: 1, timestamp: new Date() });
|
|
64
|
-
smallManager.add({ roll: 2, timestamp: new Date() });
|
|
65
|
-
smallManager.add({ roll: 3, timestamp: new Date() }); // should push out oldest
|
|
66
|
-
expect(smallManager.length).toBe(2);
|
|
67
|
-
expect(smallManager.full.map((r) => r.roll)).toEqual([2, 3]);
|
|
68
|
-
});
|
|
69
|
-
it("should return last N records with last()", () => {
|
|
70
|
-
manager.add(dieRoll);
|
|
71
|
-
manager.add(modifiedDieRoll);
|
|
72
|
-
manager.add(targetDieRoll);
|
|
73
|
-
const lastOne = manager.last();
|
|
74
|
-
expect(lastOne).toEqual([
|
|
75
|
-
{ roll: targetDieRoll.roll, outcome: targetDieRoll.outcome },
|
|
76
|
-
]);
|
|
77
|
-
const lastTwo = manager.last(2);
|
|
78
|
-
expect(lastTwo).toEqual([
|
|
79
|
-
{ roll: modifiedDieRoll.roll, modified: modifiedDieRoll.modified },
|
|
80
|
-
{ roll: targetDieRoll.roll, outcome: targetDieRoll.outcome },
|
|
81
|
-
]);
|
|
82
|
-
const verboseLastTwo = manager.last(2, true);
|
|
83
|
-
expect(verboseLastTwo).toEqual([modifiedDieRoll, targetDieRoll]);
|
|
84
|
-
});
|
|
85
|
-
it("should report records with report()", () => {
|
|
86
|
-
manager.add(dieRoll);
|
|
87
|
-
manager.add(modifiedDieRoll);
|
|
88
|
-
manager.add(targetDieRoll);
|
|
89
|
-
expect(manager.report()).toEqual(manager.all);
|
|
90
|
-
expect(manager.report({ verbose: true })).toEqual(manager.full);
|
|
91
|
-
expect(manager.report({ limit: 2 })).toEqual(manager.all.slice(-2));
|
|
92
|
-
expect(manager.report({ limit: 2, verbose: true })).toEqual(manager.full.slice(-2));
|
|
93
|
-
});
|
|
94
|
-
it("should clear records with clear()", () => {
|
|
95
|
-
manager.add(dieRoll);
|
|
96
|
-
manager.clear();
|
|
97
|
-
expect(manager.length).toBe(0);
|
|
98
|
-
expect(manager.full).toEqual([]);
|
|
99
|
-
});
|
|
100
|
-
it("toString() should reflect last roll", () => {
|
|
101
|
-
expect(manager.toString()).toContain("empty");
|
|
102
|
-
manager.add(dieRoll);
|
|
103
|
-
const str = manager.toString();
|
|
104
|
-
expect(str).toContain("1/");
|
|
105
|
-
expect(str).toContain(dieRoll.roll.toString());
|
|
106
|
-
expect(str).toContain(dieRoll.timestamp.toISOString());
|
|
107
|
-
});
|
|
108
|
-
it("toJSON() should return full records", () => {
|
|
109
|
-
manager.add(dieRoll);
|
|
110
|
-
expect(manager.toJSON()).toEqual(manager.full);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { Outcome } from "@platonic-dice/core";
|
|
3
|
-
import { isDieRollRecord, isModifiedDieRollRecord, isTargetDieRollRecord, stripTimestamp, } from "@dice/components/die-history/internal";
|
|
4
|
-
describe("RollRecord validator", () => {
|
|
5
|
-
const now = new Date();
|
|
6
|
-
const dieRecord = {
|
|
7
|
-
roll: 5,
|
|
8
|
-
timestamp: now,
|
|
9
|
-
};
|
|
10
|
-
const modifiedRecord = {
|
|
11
|
-
roll: 10,
|
|
12
|
-
modified: 12,
|
|
13
|
-
timestamp: now,
|
|
14
|
-
};
|
|
15
|
-
const targetRecord = {
|
|
16
|
-
roll: 17,
|
|
17
|
-
outcome: Outcome.Success,
|
|
18
|
-
timestamp: now,
|
|
19
|
-
};
|
|
20
|
-
it("recognises valid shapes", () => {
|
|
21
|
-
expect(isDieRollRecord(dieRecord)).toBe(true);
|
|
22
|
-
expect(isModifiedDieRollRecord(modifiedRecord)).toBe(true);
|
|
23
|
-
expect(isTargetDieRollRecord(targetRecord)).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
it("rejects invalid shapes", () => {
|
|
26
|
-
// missing timestamp
|
|
27
|
-
expect(isDieRollRecord({ roll: 5 })).toBe(false);
|
|
28
|
-
// wrong modified type
|
|
29
|
-
expect(isModifiedDieRollRecord({ roll: 5, modified: "x" })).toBe(false);
|
|
30
|
-
// invalid outcome
|
|
31
|
-
expect(isTargetDieRollRecord({ roll: 5, outcome: "nope" })).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
it("stripTimestamp removes timestamp and preserves other fields", () => {
|
|
34
|
-
const stripped = stripTimestamp(dieRecord);
|
|
35
|
-
expect(stripped.timestamp).toBeUndefined();
|
|
36
|
-
expect(stripped.roll).toBe(dieRecord.roll);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { RollRecordFactory } from "@dice/components/die-history";
|
|
3
|
-
import { DieType } from "@platonic-dice/core";
|
|
4
|
-
describe("RollRecordFactory", () => {
|
|
5
|
-
const factory = new RollRecordFactory();
|
|
6
|
-
it("should create a valid DieRollRecord for normal rolls", () => {
|
|
7
|
-
const record = factory.createNormalRoll(DieType.D6);
|
|
8
|
-
expect(record).toMatchObject({
|
|
9
|
-
roll: expect.any(Number),
|
|
10
|
-
timestamp: expect.any(Date),
|
|
11
|
-
});
|
|
12
|
-
expect(record.roll).toBeGreaterThanOrEqual(1);
|
|
13
|
-
expect(record.roll).toBeLessThanOrEqual(6);
|
|
14
|
-
});
|
|
15
|
-
it("should create a valid ModifiedDieRollRecord for modified rolls", () => {
|
|
16
|
-
const record = factory.createModifiedRoll(DieType.D20, (n) => n + 2);
|
|
17
|
-
expect(record).toMatchObject({
|
|
18
|
-
roll: expect.any(Number),
|
|
19
|
-
modified: expect.any(Number),
|
|
20
|
-
timestamp: expect.any(Date),
|
|
21
|
-
});
|
|
22
|
-
expect(record.roll).toBeGreaterThanOrEqual(1);
|
|
23
|
-
expect(record.roll).toBeLessThanOrEqual(20);
|
|
24
|
-
expect(record.modified).toBe(record.roll + 2);
|
|
25
|
-
});
|
|
26
|
-
it("should create a valid TestDieRollRecord for test rolls", () => {
|
|
27
|
-
const record = factory.createTestRoll(DieType.D10, {
|
|
28
|
-
testType: "at_least",
|
|
29
|
-
target: 5,
|
|
30
|
-
});
|
|
31
|
-
expect(record).toMatchObject({
|
|
32
|
-
roll: expect.any(Number),
|
|
33
|
-
outcome: expect.any(String),
|
|
34
|
-
timestamp: expect.any(Date),
|
|
35
|
-
});
|
|
36
|
-
expect(record.roll).toBeGreaterThanOrEqual(1);
|
|
37
|
-
expect(record.roll).toBeLessThanOrEqual(10);
|
|
38
|
-
expect(["Success", "Failure"].map((o) => o.toLowerCase())).toContain(record.outcome.toLowerCase());
|
|
39
|
-
});
|
|
40
|
-
it("should throw an error for invalid RollType in createNormalRoll", () => {
|
|
41
|
-
expect(() => factory.createNormalRoll(DieType.D6, "InvalidRollType")).toThrow(TypeError);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/// <reference types="vitest" />
|
|
2
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
3
|
-
import { Die } from "@dice/die";
|
|
4
|
-
import { DieType, RollType, roll as coreRoll, rollMod as coreRollMod, rollTest as coreRollTest, } from "@platonic-dice/core";
|
|
5
|
-
// ------------------------
|
|
6
|
-
// MOCK @platonic-dice/core
|
|
7
|
-
// ------------------------
|
|
8
|
-
vi.mock("@platonic-dice/core", async (importOriginal) => {
|
|
9
|
-
const actual = await importOriginal();
|
|
10
|
-
return {
|
|
11
|
-
...actual,
|
|
12
|
-
roll: vi.fn(),
|
|
13
|
-
rollMod: vi.fn(),
|
|
14
|
-
rollTest: vi.fn(),
|
|
15
|
-
};
|
|
16
|
-
});
|
|
17
|
-
// ------------------------
|
|
18
|
-
// TEST SUITE
|
|
19
|
-
// ------------------------
|
|
20
|
-
describe("Die class", () => {
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
vi.clearAllMocks();
|
|
23
|
-
});
|
|
24
|
-
// ---------------------
|
|
25
|
-
// CONSTRUCTOR VALIDATION
|
|
26
|
-
// ---------------------
|
|
27
|
-
it("creates a die with a valid type", () => {
|
|
28
|
-
const die = new Die(DieType.D6);
|
|
29
|
-
expect(die.type).toBe(DieType.D6);
|
|
30
|
-
expect(die.result).toBe(undefined);
|
|
31
|
-
expect(die.history("normal")).toEqual([]);
|
|
32
|
-
});
|
|
33
|
-
it("throws for invalid die type", () => {
|
|
34
|
-
expect(() => new Die("INVALID")).toThrowError(/Invalid die type/);
|
|
35
|
-
});
|
|
36
|
-
// ---------------------
|
|
37
|
-
// FACE COUNT
|
|
38
|
-
// ---------------------
|
|
39
|
-
it("returns the correct faceCount", () => {
|
|
40
|
-
expect(new Die(DieType.D4).faceCount).toBe(4);
|
|
41
|
-
expect(new Die(DieType.D6).faceCount).toBe(6);
|
|
42
|
-
expect(new Die(DieType.D20).faceCount).toBe(20);
|
|
43
|
-
});
|
|
44
|
-
// ---------------------
|
|
45
|
-
// BASIC ROLL
|
|
46
|
-
// ---------------------
|
|
47
|
-
it("rolls and records a basic DieRollRecord", () => {
|
|
48
|
-
const die = new Die(DieType.D6);
|
|
49
|
-
coreRoll.mockReturnValue(4);
|
|
50
|
-
const result = die.roll();
|
|
51
|
-
expect(result).toBe(4);
|
|
52
|
-
expect(die.result).toBe(4);
|
|
53
|
-
const history = die.history("normal", true);
|
|
54
|
-
expect(history.length).toBe(1);
|
|
55
|
-
expect(history[0]).toMatchObject({ roll: 4 });
|
|
56
|
-
});
|
|
57
|
-
it("passes through rollType to coreRoll()", () => {
|
|
58
|
-
const die = new Die(DieType.D20);
|
|
59
|
-
coreRoll.mockReturnValue(17);
|
|
60
|
-
die.roll(RollType.Advantage);
|
|
61
|
-
expect(coreRoll).toHaveBeenCalledWith(DieType.D20, RollType.Advantage);
|
|
62
|
-
});
|
|
63
|
-
it("throws for invalid rollType", () => {
|
|
64
|
-
const die = new Die(DieType.D8);
|
|
65
|
-
expect(() => die.roll("INVALID")).toThrowError(/Invalid roll type/);
|
|
66
|
-
});
|
|
67
|
-
// ---------------------
|
|
68
|
-
// MODIFIED ROLL
|
|
69
|
-
// ---------------------
|
|
70
|
-
it("rollMod records ModifiedDieRollRecord", () => {
|
|
71
|
-
const die = new Die(DieType.D6);
|
|
72
|
-
coreRollMod.mockReturnValue({ base: 3, modified: 5 });
|
|
73
|
-
const result = die.rollMod((n) => n + 2);
|
|
74
|
-
expect(result).toBe(5);
|
|
75
|
-
expect(die.result).toBe(5);
|
|
76
|
-
const history = die.history("modifier", true);
|
|
77
|
-
expect(history.length).toBe(1);
|
|
78
|
-
expect(history[0]).toMatchObject({ roll: 3, modified: 5 });
|
|
79
|
-
});
|
|
80
|
-
// ---------------------
|
|
81
|
-
// TEST ROLL
|
|
82
|
-
// ---------------------
|
|
83
|
-
it("rollTest records TargetDieRollRecord", () => {
|
|
84
|
-
const die = new Die(DieType.D6);
|
|
85
|
-
coreRollTest.mockReturnValue({ base: 4, outcome: "success" });
|
|
86
|
-
const result = die.rollTest({ testType: "at_least", target: 3 });
|
|
87
|
-
expect(result).toBe(4);
|
|
88
|
-
expect(die.result).toBe(4);
|
|
89
|
-
const history = die.history("test", true);
|
|
90
|
-
expect(history.length).toBe(1);
|
|
91
|
-
expect(history[0]).toMatchObject({ roll: 4, outcome: "success" });
|
|
92
|
-
});
|
|
93
|
-
// ---------------------
|
|
94
|
-
// HISTORY ACCESS
|
|
95
|
-
// ---------------------
|
|
96
|
-
it("history returns timestamp-stripped records when verbose=false", () => {
|
|
97
|
-
const die = new Die(DieType.D6);
|
|
98
|
-
coreRoll.mockReturnValue(2);
|
|
99
|
-
die.roll();
|
|
100
|
-
const hist = die.history("normal", false);
|
|
101
|
-
expect(hist[0]).not.toHaveProperty("timestamp");
|
|
102
|
-
const histVerbose = die.history("normal", true);
|
|
103
|
-
expect(histVerbose[0]).toHaveProperty("timestamp");
|
|
104
|
-
});
|
|
105
|
-
// ---------------------
|
|
106
|
-
// RESET
|
|
107
|
-
// ---------------------
|
|
108
|
-
it("reset clears latest result and optionally all histories", () => {
|
|
109
|
-
const die = new Die(DieType.D6);
|
|
110
|
-
coreRoll.mockReturnValue(5);
|
|
111
|
-
die.roll();
|
|
112
|
-
die.reset();
|
|
113
|
-
expect(die.result).toBeUndefined();
|
|
114
|
-
die.roll();
|
|
115
|
-
die.reset(true);
|
|
116
|
-
expect(die.result).toBeUndefined();
|
|
117
|
-
expect(die.history("normal")).toEqual([]);
|
|
118
|
-
expect(die.history("modifier")).toEqual([]);
|
|
119
|
-
expect(die.history("test")).toEqual([]);
|
|
120
|
-
});
|
|
121
|
-
// ---------------------
|
|
122
|
-
// toString / toJSON
|
|
123
|
-
// ---------------------
|
|
124
|
-
it("toString reports correctly", () => {
|
|
125
|
-
const die = new Die(DieType.D6);
|
|
126
|
-
expect(die.toString()).toMatch(/not rolled yet/);
|
|
127
|
-
coreRoll.mockReturnValue(3);
|
|
128
|
-
die.roll();
|
|
129
|
-
expect(die.toString()).toMatch(/latest=3/);
|
|
130
|
-
});
|
|
131
|
-
it("toJSON returns all histories keyed by type", () => {
|
|
132
|
-
const die = new Die(DieType.D6);
|
|
133
|
-
coreRoll.mockReturnValue(4);
|
|
134
|
-
die.roll();
|
|
135
|
-
const json = die.toJSON();
|
|
136
|
-
expect(json).toHaveProperty("normal");
|
|
137
|
-
expect(json.normal.length).toBe(1);
|
|
138
|
-
expect(json.normal[0]).toMatchObject({ roll: 4 });
|
|
139
|
-
});
|
|
140
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { Outcome } from "@platonic-dice/core/entities";
|
|
3
|
-
describe("RollRecord types (runtime shape validation)", () => {
|
|
4
|
-
const now = new Date();
|
|
5
|
-
const dieRecord = {
|
|
6
|
-
roll: 5,
|
|
7
|
-
timestamp: now,
|
|
8
|
-
};
|
|
9
|
-
const modifiedRecord = {
|
|
10
|
-
roll: 10,
|
|
11
|
-
modified: 12,
|
|
12
|
-
timestamp: now,
|
|
13
|
-
};
|
|
14
|
-
const targetRecord = {
|
|
15
|
-
roll: 17,
|
|
16
|
-
outcome: Outcome.Success,
|
|
17
|
-
timestamp: now,
|
|
18
|
-
};
|
|
19
|
-
it("should have the correct shape for DieRollRecord", () => {
|
|
20
|
-
expect(dieRecord).toHaveProperty("roll");
|
|
21
|
-
expect(dieRecord).toHaveProperty("timestamp");
|
|
22
|
-
expect(dieRecord.timestamp).toBeInstanceOf(Date);
|
|
23
|
-
expect(Object.keys(dieRecord)).toHaveLength(2);
|
|
24
|
-
});
|
|
25
|
-
it("should have the correct shape for ModifiedDieRollRecord", () => {
|
|
26
|
-
expect(modifiedRecord).toHaveProperty("roll");
|
|
27
|
-
expect(modifiedRecord).toHaveProperty("modified");
|
|
28
|
-
expect(modifiedRecord).toHaveProperty("timestamp");
|
|
29
|
-
expect(modifiedRecord.timestamp).toBeInstanceOf(Date);
|
|
30
|
-
});
|
|
31
|
-
it("should have the correct shape for TargetDieRollRecord", () => {
|
|
32
|
-
expect(targetRecord).toHaveProperty("roll");
|
|
33
|
-
expect(targetRecord).toHaveProperty("outcome");
|
|
34
|
-
expect(targetRecord).toHaveProperty("timestamp");
|
|
35
|
-
expect(targetRecord.timestamp).toBeInstanceOf(Date);
|
|
36
|
-
});
|
|
37
|
-
it("should be compatible with the RollRecord union", () => {
|
|
38
|
-
const records = [dieRecord, modifiedRecord, targetRecord];
|
|
39
|
-
expect(records).toHaveLength(3);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./roll-record-storage";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./die-history";
|
package/dist/src/types/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./roll-record.types";
|
|
File without changes
|
|
File without changes
|