@vworlds/vecs 1.0.0 → 1.0.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/dist/package.json +6 -2
- package/package.json +6 -2
- package/.claude/settings.json +0 -12
- package/.devcontainer/devcontainer.json +0 -22
- package/.github/workflows/publish.yml +0 -32
- package/src/component.ts +0 -180
- package/src/entity.ts +0 -276
- package/src/index.ts +0 -6
- package/src/phase.ts +0 -49
- package/src/system.ts +0 -693
- package/src/util/array_map.ts +0 -93
- package/src/util/bitset.ts +0 -199
- package/src/util/events.ts +0 -95
- package/src/util/ordered_set.ts +0 -82
- package/src/world.ts +0 -534
- package/tests/_helpers.ts +0 -30
- package/tests/array_map.test.ts +0 -68
- package/tests/bitset.test.ts +0 -127
- package/tests/component.test.ts +0 -104
- package/tests/entity.test.ts +0 -179
- package/tests/events.test.ts +0 -48
- package/tests/ordered_set.test.ts +0 -153
- package/tests/setup.ts +0 -6
- package/tests/system.test.ts +0 -800
- package/tests/world.test.ts +0 -174
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -9
package/tests/bitset.test.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { Bitset, BitPtr } from "../src/util/bitset.js";
|
|
3
|
-
|
|
4
|
-
describe("Bitset", () => {
|
|
5
|
-
it("starts empty", () => {
|
|
6
|
-
const b = new Bitset();
|
|
7
|
-
expect(b.has(0)).toBe(false);
|
|
8
|
-
expect(b.has(31)).toBe(false);
|
|
9
|
-
expect(b.has(64)).toBe(false);
|
|
10
|
-
expect(b.indices()).toEqual([]);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("adds and checks single bits", () => {
|
|
14
|
-
const b = new Bitset();
|
|
15
|
-
b.add(3);
|
|
16
|
-
b.add(33);
|
|
17
|
-
b.add(100);
|
|
18
|
-
expect(b.has(3)).toBe(true);
|
|
19
|
-
expect(b.has(33)).toBe(true);
|
|
20
|
-
expect(b.has(100)).toBe(true);
|
|
21
|
-
expect(b.has(2)).toBe(false);
|
|
22
|
-
expect(b.has(34)).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("returns indices in ascending order", () => {
|
|
26
|
-
const b = new Bitset();
|
|
27
|
-
b.add(100);
|
|
28
|
-
b.add(0);
|
|
29
|
-
b.add(31);
|
|
30
|
-
b.add(32);
|
|
31
|
-
expect(b.indices()).toEqual([0, 31, 32, 100]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("delete clears a bit", () => {
|
|
35
|
-
const b = new Bitset();
|
|
36
|
-
b.add(0);
|
|
37
|
-
b.add(64);
|
|
38
|
-
expect(b.has(64)).toBe(true);
|
|
39
|
-
b.delete(64);
|
|
40
|
-
expect(b.has(64)).toBe(false);
|
|
41
|
-
expect(b.has(0)).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("delete trims trailing zero words", () => {
|
|
45
|
-
const b = new Bitset();
|
|
46
|
-
b.add(0);
|
|
47
|
-
b.add(32);
|
|
48
|
-
b.add(64);
|
|
49
|
-
expect(b["bits"].length).toBe(3);
|
|
50
|
-
b.delete(64);
|
|
51
|
-
expect(b["bits"].length).toBe(2);
|
|
52
|
-
b.delete(32);
|
|
53
|
-
expect(b["bits"].length).toBe(1);
|
|
54
|
-
b.delete(0);
|
|
55
|
-
expect(b["bits"].length).toBe(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("delete on absent word is a no-op", () => {
|
|
59
|
-
const b = new Bitset();
|
|
60
|
-
b.delete(50); // never been added — array is empty
|
|
61
|
-
expect(b.has(50)).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("equal compares bit-by-bit", () => {
|
|
65
|
-
const a = new Bitset();
|
|
66
|
-
const b = new Bitset();
|
|
67
|
-
a.add(1);
|
|
68
|
-
a.add(33);
|
|
69
|
-
b.add(33);
|
|
70
|
-
b.add(1);
|
|
71
|
-
expect(a.equal(b)).toBe(true);
|
|
72
|
-
b.add(2);
|
|
73
|
-
expect(a.equal(b)).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("hasBitset checks subset relation", () => {
|
|
77
|
-
const a = new Bitset();
|
|
78
|
-
const b = new Bitset();
|
|
79
|
-
a.add(1);
|
|
80
|
-
a.add(2);
|
|
81
|
-
a.add(33);
|
|
82
|
-
b.add(1);
|
|
83
|
-
b.add(33);
|
|
84
|
-
expect(a.hasBitset(b)).toBe(true);
|
|
85
|
-
expect(b.hasBitset(a)).toBe(false);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("hasBitset returns false when other has bits in higher words than this", () => {
|
|
89
|
-
const a = new Bitset();
|
|
90
|
-
const b = new Bitset();
|
|
91
|
-
a.add(0);
|
|
92
|
-
b.add(0);
|
|
93
|
-
b.add(64);
|
|
94
|
-
expect(a.hasBitset(b)).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("forEach yields each set bit exactly once", () => {
|
|
98
|
-
const b = new Bitset();
|
|
99
|
-
[0, 1, 32, 33, 100].forEach((n) => b.add(n));
|
|
100
|
-
const seen: number[] = [];
|
|
101
|
-
b.forEach((n) => seen.push(n));
|
|
102
|
-
expect(seen).toEqual([0, 1, 32, 33, 100]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("BitPtr fast path matches add/has results", () => {
|
|
106
|
-
const b = new Bitset();
|
|
107
|
-
const ptr = new BitPtr(45);
|
|
108
|
-
b.addBit(ptr);
|
|
109
|
-
expect(b.hasBit(ptr)).toBe(true);
|
|
110
|
-
expect(b.has(45)).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("BitPtr.equals identifies same bit position", () => {
|
|
114
|
-
expect(new BitPtr(7).equals(new BitPtr(7))).toBe(true);
|
|
115
|
-
expect(new BitPtr(7).equals(new BitPtr(8))).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("addIndexBitmask and setIndexBitmask manipulate raw words", () => {
|
|
119
|
-
const b = new Bitset();
|
|
120
|
-
b.addIndexBitmask(0, 0b1010);
|
|
121
|
-
expect(b.has(1)).toBe(true);
|
|
122
|
-
expect(b.has(3)).toBe(true);
|
|
123
|
-
b.setIndexBitmask(0, 0b0001);
|
|
124
|
-
expect(b.has(1)).toBe(false);
|
|
125
|
-
expect(b.has(0)).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
});
|
package/tests/component.test.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { World, Component } from "../src/index.js";
|
|
3
|
-
import { makeWorldWithFlushPhase } from "./_helpers.js";
|
|
4
|
-
|
|
5
|
-
class Health extends Component {
|
|
6
|
-
hp = 10;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe("Component", () => {
|
|
10
|
-
it("type is shorthand for meta.type", () => {
|
|
11
|
-
const w = new World();
|
|
12
|
-
w.registerComponent(Health, 42);
|
|
13
|
-
const e = w.createEntity();
|
|
14
|
-
const h = e.add(Health);
|
|
15
|
-
expect(h.type).toBe(42);
|
|
16
|
-
expect(h.type).toBe(h.meta.type);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("bitPtr is shorthand for meta.bitPtr", () => {
|
|
20
|
-
const w = new World();
|
|
21
|
-
w.registerComponent(Health);
|
|
22
|
-
const h = w.createEntity().add(Health);
|
|
23
|
-
expect(h.bitPtr).toBe(h.meta.bitPtr);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("toString returns the component name", () => {
|
|
27
|
-
const w = new World();
|
|
28
|
-
w.registerComponent(Health, "HP");
|
|
29
|
-
const h = w.createEntity().add(Health);
|
|
30
|
-
expect(h.toString()).toBe("HP");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("modified() queues an onSet hook delivery", () => {
|
|
34
|
-
const env = makeWorldWithFlushPhase();
|
|
35
|
-
env.w.registerComponent(Health);
|
|
36
|
-
const onSet = vi.fn();
|
|
37
|
-
env.w.hook(Health).onSet(onSet);
|
|
38
|
-
env.start();
|
|
39
|
-
const h = env.w.createEntity().add(Health, false);
|
|
40
|
-
h.modified();
|
|
41
|
-
env.tick();
|
|
42
|
-
expect(onSet).toHaveBeenCalledWith(h);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("modified() is coalesced — onSet fires once even if called twice", () => {
|
|
46
|
-
const env = makeWorldWithFlushPhase();
|
|
47
|
-
env.w.registerComponent(Health);
|
|
48
|
-
const onSet = vi.fn();
|
|
49
|
-
env.w.hook(Health).onSet(onSet);
|
|
50
|
-
env.start();
|
|
51
|
-
const h = env.w.createEntity().add(Health, false);
|
|
52
|
-
h.modified();
|
|
53
|
-
h.modified();
|
|
54
|
-
h.modified();
|
|
55
|
-
env.tick();
|
|
56
|
-
expect(onSet).toHaveBeenCalledTimes(1);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("Hook", () => {
|
|
61
|
-
it("onAdd fires when a component is first attached", () => {
|
|
62
|
-
const env = makeWorldWithFlushPhase();
|
|
63
|
-
env.w.registerComponent(Health);
|
|
64
|
-
const onAdd = vi.fn();
|
|
65
|
-
env.w.hook(Health).onAdd(onAdd);
|
|
66
|
-
env.start();
|
|
67
|
-
const h = env.w.createEntity().add(Health);
|
|
68
|
-
expect(onAdd).toHaveBeenCalledWith(h);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("onRemove fires when a component is removed", () => {
|
|
72
|
-
const env = makeWorldWithFlushPhase();
|
|
73
|
-
env.w.registerComponent(Health);
|
|
74
|
-
const onRemove = vi.fn();
|
|
75
|
-
env.w.hook(Health).onRemove(onRemove);
|
|
76
|
-
env.start();
|
|
77
|
-
const e = env.w.createEntity();
|
|
78
|
-
const h = e.add(Health);
|
|
79
|
-
e.remove(Health);
|
|
80
|
-
expect(onRemove).toHaveBeenCalledWith(h);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("onRemove fires when the entity is destroyed", () => {
|
|
84
|
-
const env = makeWorldWithFlushPhase();
|
|
85
|
-
env.w.registerComponent(Health);
|
|
86
|
-
const onRemove = vi.fn();
|
|
87
|
-
env.w.hook(Health).onRemove(onRemove);
|
|
88
|
-
env.start();
|
|
89
|
-
const e = env.w.createEntity();
|
|
90
|
-
e.add(Health);
|
|
91
|
-
e.destroy();
|
|
92
|
-
expect(onRemove).toHaveBeenCalledTimes(1);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("hooks are chainable on the same Hook object", () => {
|
|
96
|
-
const w = new World();
|
|
97
|
-
w.registerComponent(Health);
|
|
98
|
-
const a = vi.fn();
|
|
99
|
-
const b = vi.fn();
|
|
100
|
-
const c = vi.fn();
|
|
101
|
-
const result = w.hook(Health).onAdd(a).onRemove(b).onSet(c);
|
|
102
|
-
expect(result).toBe(w.hook(Health));
|
|
103
|
-
});
|
|
104
|
-
});
|
package/tests/entity.test.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { World, Component } from "../src/index.js";
|
|
3
|
-
import { makeWorldWithFlushPhase } from "./_helpers.js";
|
|
4
|
-
|
|
5
|
-
class Position extends Component {
|
|
6
|
-
x = 0;
|
|
7
|
-
y = 0;
|
|
8
|
-
}
|
|
9
|
-
class Velocity extends Component {
|
|
10
|
-
vx = 0;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("Entity — components", () => {
|
|
14
|
-
it("add returns a typed instance bound to the entity", () => {
|
|
15
|
-
const w = new World();
|
|
16
|
-
w.registerComponent(Position);
|
|
17
|
-
const e = w.createEntity();
|
|
18
|
-
const pos = e.add(Position);
|
|
19
|
-
expect(pos).toBeInstanceOf(Position);
|
|
20
|
-
expect(pos.entity).toBe(e);
|
|
21
|
-
expect(pos.x).toBe(0);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("add is idempotent — same instance is returned on repeat", () => {
|
|
25
|
-
const w = new World();
|
|
26
|
-
w.registerComponent(Position);
|
|
27
|
-
const e = w.createEntity();
|
|
28
|
-
const a = e.add(Position);
|
|
29
|
-
const b = e.add(Position);
|
|
30
|
-
expect(a).toBe(b);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("add(typeId) works with numeric type ids", () => {
|
|
34
|
-
const w = new World();
|
|
35
|
-
w.registerComponent(Position, 5);
|
|
36
|
-
const e = w.createEntity();
|
|
37
|
-
const pos = e.add(5);
|
|
38
|
-
expect(pos).toBeInstanceOf(Position);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("get returns the component or undefined", () => {
|
|
42
|
-
const w = new World();
|
|
43
|
-
w.registerComponent(Position);
|
|
44
|
-
const e = w.createEntity();
|
|
45
|
-
expect(e.get(Position)).toBeUndefined();
|
|
46
|
-
const pos = e.add(Position);
|
|
47
|
-
expect(e.get(Position)).toBe(pos);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("remove detaches a component", () => {
|
|
51
|
-
const w = new World();
|
|
52
|
-
w.registerComponent(Position);
|
|
53
|
-
const e = w.createEntity();
|
|
54
|
-
e.add(Position);
|
|
55
|
-
e.remove(Position);
|
|
56
|
-
expect(e.get(Position)).toBeUndefined();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("get(Class, true) recovers a component removed in this frame", () => {
|
|
60
|
-
const w = new World();
|
|
61
|
-
w.registerComponent(Position);
|
|
62
|
-
const e = w.createEntity();
|
|
63
|
-
const pos = e.add(Position);
|
|
64
|
-
e.remove(Position);
|
|
65
|
-
expect(e.get(Position)).toBeUndefined();
|
|
66
|
-
expect(e.get(Position, true)).toBe(pos);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("componentBitmask reflects added/removed components", () => {
|
|
70
|
-
const w = new World();
|
|
71
|
-
w.registerComponent(Position);
|
|
72
|
-
w.registerComponent(Velocity);
|
|
73
|
-
const e = w.createEntity();
|
|
74
|
-
e.add(Position);
|
|
75
|
-
e.add(Velocity);
|
|
76
|
-
expect(e.componentBitmask.has(w.getComponentType(Position))).toBe(true);
|
|
77
|
-
expect(e.componentBitmask.has(w.getComponentType(Velocity))).toBe(true);
|
|
78
|
-
e.remove(Position);
|
|
79
|
-
expect(e.componentBitmask.has(w.getComponentType(Position))).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("empty reflects whether any components remain", () => {
|
|
83
|
-
const w = new World();
|
|
84
|
-
w.registerComponent(Position);
|
|
85
|
-
const e = w.createEntity();
|
|
86
|
-
expect(e.empty).toBe(true);
|
|
87
|
-
e.add(Position);
|
|
88
|
-
expect(e.empty).toBe(false);
|
|
89
|
-
e.remove(Position);
|
|
90
|
-
expect(e.empty).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("forEachComponent visits every attached component", () => {
|
|
94
|
-
const w = new World();
|
|
95
|
-
w.registerComponent(Position);
|
|
96
|
-
w.registerComponent(Velocity);
|
|
97
|
-
const e = w.createEntity();
|
|
98
|
-
e.add(Position);
|
|
99
|
-
e.add(Velocity);
|
|
100
|
-
const seen: string[] = [];
|
|
101
|
-
e.forEachComponent((c) => seen.push(c.toString()));
|
|
102
|
-
expect(seen.sort()).toEqual(["Position", "Velocity"]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("toString returns Entity{eid}", () => {
|
|
106
|
-
const w = new World();
|
|
107
|
-
const e = w.createEntity();
|
|
108
|
-
expect(e.toString()).toBe(`Entity${e.eid}`);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe("Entity — lifecycle and hierarchy", () => {
|
|
113
|
-
it("destroy emits the 'destroy' event", () => {
|
|
114
|
-
const env = makeWorldWithFlushPhase();
|
|
115
|
-
env.start();
|
|
116
|
-
const e = env.w.createEntity();
|
|
117
|
-
const cb = vi.fn();
|
|
118
|
-
e.events.on("destroy", cb);
|
|
119
|
-
e.destroy();
|
|
120
|
-
env.tick();
|
|
121
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("destroy removes the entity from the world map", () => {
|
|
125
|
-
const env = makeWorldWithFlushPhase();
|
|
126
|
-
env.start();
|
|
127
|
-
const e = env.w.createEntity();
|
|
128
|
-
e.destroy();
|
|
129
|
-
expect(env.w.entity(e.eid)).toBeUndefined();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("destroy recursively destroys children", () => {
|
|
133
|
-
const env = makeWorldWithFlushPhase();
|
|
134
|
-
env.start();
|
|
135
|
-
const parent = env.w.createEntity();
|
|
136
|
-
const child = env.w.createEntity();
|
|
137
|
-
child.parent = parent;
|
|
138
|
-
parent.children.add(child);
|
|
139
|
-
|
|
140
|
-
const childDestroyed = vi.fn();
|
|
141
|
-
child.events.on("destroy", childDestroyed);
|
|
142
|
-
parent.destroy();
|
|
143
|
-
env.tick();
|
|
144
|
-
expect(childDestroyed).toHaveBeenCalled();
|
|
145
|
-
expect(env.w.entity(child.eid)).toBeUndefined();
|
|
146
|
-
expect(env.w.entity(parent.eid)).toBeUndefined();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("destroy unlinks from parent and triggers parent's archetype update", () => {
|
|
150
|
-
const env = makeWorldWithFlushPhase();
|
|
151
|
-
env.start();
|
|
152
|
-
const parent = env.w.createEntity();
|
|
153
|
-
const child = env.w.createEntity();
|
|
154
|
-
child.parent = parent;
|
|
155
|
-
parent.children.add(child);
|
|
156
|
-
|
|
157
|
-
child.destroy();
|
|
158
|
-
env.tick();
|
|
159
|
-
expect(parent.children.has(child)).toBe(false);
|
|
160
|
-
expect(child.parent).toBeUndefined();
|
|
161
|
-
expect(env.w.entity(parent.eid)).toBe(parent);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("children set is created lazily", () => {
|
|
165
|
-
const w = new World();
|
|
166
|
-
const e = w.createEntity();
|
|
167
|
-
expect(e["_children"]).toBeUndefined();
|
|
168
|
-
const c = e.children;
|
|
169
|
-
expect(c).toBeInstanceOf(Set);
|
|
170
|
-
expect(e.children).toBe(c); // memoized
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("properties is a free-form map", () => {
|
|
174
|
-
const w = new World();
|
|
175
|
-
const e = w.createEntity();
|
|
176
|
-
e.properties.set("kind", "bullet");
|
|
177
|
-
expect(e.properties.get("kind")).toBe("bullet");
|
|
178
|
-
});
|
|
179
|
-
});
|
package/tests/events.test.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { Events } from "../src/util/events.js";
|
|
3
|
-
|
|
4
|
-
type Map = {
|
|
5
|
-
hello(name: string): void;
|
|
6
|
-
count(n: number): void;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
describe("Events", () => {
|
|
10
|
-
it("calls listeners with their typed args", () => {
|
|
11
|
-
const e = new Events<Map>();
|
|
12
|
-
const greet = vi.fn();
|
|
13
|
-
e.on("hello", greet);
|
|
14
|
-
e.emit("hello", "world");
|
|
15
|
-
expect(greet).toHaveBeenCalledWith("world");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("once fires only on the first emit", () => {
|
|
19
|
-
const e = new Events<Map>();
|
|
20
|
-
const cb = vi.fn();
|
|
21
|
-
e.once("count", cb);
|
|
22
|
-
e.emit("count", 1);
|
|
23
|
-
e.emit("count", 2);
|
|
24
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
25
|
-
expect(cb).toHaveBeenCalledWith(1);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("off / removeListener detaches a single listener", () => {
|
|
29
|
-
const e = new Events<Map>();
|
|
30
|
-
const cb = vi.fn();
|
|
31
|
-
e.on("hello", cb);
|
|
32
|
-
e.off("hello", cb);
|
|
33
|
-
e.emit("hello", "x");
|
|
34
|
-
expect(cb).not.toHaveBeenCalled();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("removeAllListeners detaches every handler", () => {
|
|
38
|
-
const e = new Events<Map>();
|
|
39
|
-
const a = vi.fn();
|
|
40
|
-
const b = vi.fn();
|
|
41
|
-
e.on("hello", a);
|
|
42
|
-
e.on("hello", b);
|
|
43
|
-
e.removeAllListeners("hello");
|
|
44
|
-
e.emit("hello", "x");
|
|
45
|
-
expect(a).not.toHaveBeenCalled();
|
|
46
|
-
expect(b).not.toHaveBeenCalled();
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { OrderedSet } from "../src/util/ordered_set.js";
|
|
3
|
-
|
|
4
|
-
const numCmp = (a: number, b: number) => a - b;
|
|
5
|
-
const strCmp = (a: string, b: string) => a.localeCompare(b);
|
|
6
|
-
|
|
7
|
-
describe("OrderedSet", () => {
|
|
8
|
-
it("starts empty", () => {
|
|
9
|
-
const s = new OrderedSet<number>(numCmp);
|
|
10
|
-
expect(s.size).toBe(0);
|
|
11
|
-
expect(s.has(1)).toBe(false);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("add/has round trip", () => {
|
|
15
|
-
const s = new OrderedSet<number>(numCmp);
|
|
16
|
-
s.add(3);
|
|
17
|
-
expect(s.has(3)).toBe(true);
|
|
18
|
-
expect(s.size).toBe(1);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("add is idempotent", () => {
|
|
22
|
-
const s = new OrderedSet<number>(numCmp);
|
|
23
|
-
s.add(5).add(5).add(5);
|
|
24
|
-
expect(s.size).toBe(1);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("add returns this for chaining", () => {
|
|
28
|
-
const s = new OrderedSet<number>(numCmp);
|
|
29
|
-
expect(s.add(1).add(2)).toBe(s);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("maintains sorted order", () => {
|
|
33
|
-
const s = new OrderedSet<number>(numCmp);
|
|
34
|
-
s.add(30).add(10).add(20).add(5);
|
|
35
|
-
expect([...s]).toEqual([5, 10, 20, 30]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("delete removes existing element and returns true", () => {
|
|
39
|
-
const s = new OrderedSet<number>(numCmp);
|
|
40
|
-
s.add(1).add(2).add(3);
|
|
41
|
-
expect(s.delete(2)).toBe(true);
|
|
42
|
-
expect(s.has(2)).toBe(false);
|
|
43
|
-
expect(s.size).toBe(2);
|
|
44
|
-
expect([...s]).toEqual([1, 3]);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("delete on missing element returns false", () => {
|
|
48
|
-
const s = new OrderedSet<number>(numCmp);
|
|
49
|
-
s.add(1);
|
|
50
|
-
expect(s.delete(99)).toBe(false);
|
|
51
|
-
expect(s.size).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("clear empties the set", () => {
|
|
55
|
-
const s = new OrderedSet<number>(numCmp);
|
|
56
|
-
s.add(1).add(2).add(3);
|
|
57
|
-
s.clear();
|
|
58
|
-
expect(s.size).toBe(0);
|
|
59
|
-
expect(s.has(1)).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("forEach visits values in sorted order", () => {
|
|
63
|
-
const s = new OrderedSet<number>(numCmp);
|
|
64
|
-
s.add(3).add(1).add(2);
|
|
65
|
-
const seen: number[] = [];
|
|
66
|
-
s.forEach((v) => seen.push(v));
|
|
67
|
-
expect(seen).toEqual([1, 2, 3]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("forEach passes value, value, set", () => {
|
|
71
|
-
const s = new OrderedSet<number>(numCmp);
|
|
72
|
-
s.add(42);
|
|
73
|
-
s.forEach((v, v2, set) => {
|
|
74
|
-
expect(v).toBe(42);
|
|
75
|
-
expect(v2).toBe(42);
|
|
76
|
-
expect(set).toBe(s);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("forEach respects thisArg", () => {
|
|
81
|
-
const s = new OrderedSet<number>(numCmp);
|
|
82
|
-
s.add(1);
|
|
83
|
-
const ctx = { called: false };
|
|
84
|
-
s.forEach(function (this: typeof ctx) {
|
|
85
|
-
this.called = true;
|
|
86
|
-
}, ctx);
|
|
87
|
-
expect(ctx.called).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("[Symbol.iterator] yields values in sorted order", () => {
|
|
91
|
-
const s = new OrderedSet<number>(numCmp);
|
|
92
|
-
s.add(9).add(3).add(6);
|
|
93
|
-
expect([...s]).toEqual([3, 6, 9]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("values() yields sorted values", () => {
|
|
97
|
-
const s = new OrderedSet<number>(numCmp);
|
|
98
|
-
s.add(2).add(1).add(3);
|
|
99
|
-
expect([...s.values()]).toEqual([1, 2, 3]);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("keys() yields sorted values (same as values)", () => {
|
|
103
|
-
const s = new OrderedSet<number>(numCmp);
|
|
104
|
-
s.add(2).add(1).add(3);
|
|
105
|
-
expect([...s.keys()]).toEqual([1, 2, 3]);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("entries() yields [value, value] pairs in sorted order", () => {
|
|
109
|
-
const s = new OrderedSet<number>(numCmp);
|
|
110
|
-
s.add(2).add(1);
|
|
111
|
-
expect([...s.entries()]).toEqual([
|
|
112
|
-
[1, 1],
|
|
113
|
-
[2, 2],
|
|
114
|
-
]);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("works with string comparator", () => {
|
|
118
|
-
const s = new OrderedSet<string>(strCmp);
|
|
119
|
-
s.add("banana").add("apple").add("cherry");
|
|
120
|
-
expect([...s]).toEqual(["apple", "banana", "cherry"]);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("works with object comparator", () => {
|
|
124
|
-
type Item = { priority: number; name: string };
|
|
125
|
-
const s = new OrderedSet<Item>((a, b) => a.priority - b.priority);
|
|
126
|
-
s.add({ priority: 3, name: "c" });
|
|
127
|
-
s.add({ priority: 1, name: "a" });
|
|
128
|
-
s.add({ priority: 2, name: "b" });
|
|
129
|
-
const names = [...s].map((x) => x.name);
|
|
130
|
-
expect(names).toEqual(["a", "b", "c"]);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("binary search handles first and last element", () => {
|
|
134
|
-
const s = new OrderedSet<number>(numCmp);
|
|
135
|
-
for (let i = 1; i <= 10; i++) s.add(i);
|
|
136
|
-
expect(s.has(1)).toBe(true);
|
|
137
|
-
expect(s.has(10)).toBe(true);
|
|
138
|
-
expect(s.delete(1)).toBe(true);
|
|
139
|
-
expect(s.delete(10)).toBe(true);
|
|
140
|
-
expect([...s]).toEqual([2, 3, 4, 5, 6, 7, 8, 9]);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("Symbol.toStringTag is 'OrderedSet'", () => {
|
|
144
|
-
const s = new OrderedSet<number>(numCmp);
|
|
145
|
-
expect(s[Symbol.toStringTag]).toBe("OrderedSet");
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("is assignable to Set<T>", () => {
|
|
149
|
-
const s: Set<number> = new OrderedSet<number>(numCmp);
|
|
150
|
-
s.add(1).add(2);
|
|
151
|
-
expect(s.size).toBe(2);
|
|
152
|
-
});
|
|
153
|
-
});
|