Skip to content

Commit 1f3c3ed

Browse files
committed
fixup! fixup! wip! Implement partial-unordered comparison mode
1 parent cfa39e1 commit 1f3c3ed

30 files changed

+241
-223
lines changed

src/accessors/element.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { deeplyEqual, strictlyEqual, unequal } from '../comparison.ts'
1+
import { deeplyEqual, strictlyEqual, unequal, type Mode } from '../comparison.ts'
22
import type { Encoder } from '../encoder.ts'
33
import { staticTypeTable } from '../serialization-types.ts'
44
import {
@@ -68,10 +68,10 @@ export class ElementAccessor implements CommonRepresentation, DeepFunctionality
6868
yield this.#value
6969
}
7070

71-
compare(other: ValueRepresentation) {
71+
compare(other: ValueRepresentation, mode: Mode) {
7272
if (!(#value in other)) return unequal
7373
if (this.#index !== other.#index) return unequal
74-
return this.#value.compare(other.#value)
74+
return this.#value.compare(other.#value, mode)
7575
}
7676

7777
finalFormat(formatter: Formatter): void {

src/accessors/iterator-value.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { unequal } from '../comparison.ts'
1+
import { unequal, type Mode } from '../comparison.ts'
22
import { partial, type SerializationResult } from '../serialization-result.ts'
33
import type { Encoder } from '../encoder.ts'
44
import type {
@@ -31,10 +31,10 @@ export class IteratorValueAccessor implements CommonRepresentation, DeepFunction
3131
yield this.#value
3232
}
3333

34-
compare(other: ValueRepresentation) {
34+
compare(other: ValueRepresentation, mode: Mode) {
3535
if (!(#value in other)) return unequal
3636
if (this.#index !== other.#index) return unequal
37-
return this.#value.compare(other.#value)
37+
return this.#value.compare(other.#value, mode)
3838
}
3939

4040
finalFormat(formatter: Formatter): void {

src/accessors/map-entry.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import never from 'never'
2-
import { possiblyEqual, strictlyEqual, unequal } from '../comparison.ts'
2+
import { possiblyEqual, strictlyEqual, unequal, type Mode } from '../comparison.ts'
33
import { DeserializationContext } from '../deserialization-context.ts'
44
import { type SerializationResult, partial } from '../serialization-result.ts'
55
import type {
@@ -44,13 +44,15 @@ export class MapEntryAccessor implements CommonRepresentation, DeepFunctionality
4444
yield this.#value
4545
}
4646

47-
compare(other: ValueRepresentation) {
47+
compare(other: ValueRepresentation, mode: Mode) {
4848
if (!(#value in other)) return unequal
4949

50-
const keyComparison = this.#key.compare(other.#key)
50+
// Always compare keys in full mode; allowing keys to be partially equal
51+
// will make it impossible to distinguish between different map entries.
52+
const keyComparison = this.#key.compare(other.#key, 'full')
5153
if (keyComparison !== strictlyEqual && keyComparison !== possiblyEqual) return keyComparison
5254

53-
return this.#value.compare(other.#value)
55+
return this.#value.compare(other.#value, mode)
5456
}
5557

5658
formatAfterIteration(formatter: Formatter, value: ValueRepresentation): void {

src/accessors/property.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Comparison, comparable, strictlyEqual, unequal, possiblyEqual } from '../comparison.ts'
1+
import { type Comparison, comparable, strictlyEqual, unequal, possiblyEqual, type Mode } from '../comparison.ts'
22
import type { Encoder } from '../encoder.ts'
33
import { type SerializationResult, partial } from '../serialization-result.ts'
44
import type {
@@ -36,11 +36,11 @@ export class NamedPropertyAccessor implements CommonRepresentation, DeepFunction
3636
yield this.#value
3737
}
3838

39-
compare(other: ValueRepresentation): Comparison {
39+
compare(other: ValueRepresentation, mode: Mode): Comparison {
4040
if (!(#value in other)) return unequal
4141
if (this.#key !== other.#key) return unequal
4242

43-
return this.#value.compare(other.#value)
43+
return this.#value.compare(other.#value, mode)
4444
}
4545

4646
preformat(formatter: Formatter) {
@@ -135,12 +135,12 @@ export class SymbolPropertyAccessor implements CommonRepresentation, DeepFunctio
135135
return new SymbolPropertyGroup(properties)
136136
}
137137

138-
compare(other: ValueRepresentation): Comparison {
138+
compare(other: ValueRepresentation, mode: Mode): Comparison {
139139
if (!(#value in other)) return unequal
140140
const comparison = this.#key.compare(other.#key)
141141
if (comparison !== strictlyEqual && comparison !== possiblyEqual) return unequal
142142

143-
return this.#value.compare(other.#value)
143+
return this.#value.compare(other.#value, mode)
144144
}
145145

146146
preformat(formatter: Formatter) {

src/accessors/test/element.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ test('ElementAccessor - compare returns unequal for non-ElementAccessor', (t) =>
108108
},
109109
}
110110

111-
t.is(element.compare(nonElement as unknown as ValueRepresentation), unequal)
111+
t.is(element.compare(nonElement as unknown as ValueRepresentation, 'full'), unequal)
112112
})
113113

114114
test('ElementAccessor - compare returns unequal for different indices', (t) => {
@@ -117,7 +117,7 @@ test('ElementAccessor - compare returns unequal for different indices', (t) => {
117117
const element1 = new ElementAccessor(5, value)
118118
const element2 = new ElementAccessor(10, value)
119119

120-
t.is(element1.compare(element2), unequal)
120+
t.is(element1.compare(element2, 'full'), unequal)
121121
})
122122

123123
test('ElementAccessor - compare delegates to value comparison when indices match', (t) => {
@@ -128,12 +128,12 @@ test('ElementAccessor - compare delegates to value comparison when indices match
128128
const element2 = new ElementAccessor(5, value2)
129129

130130
// Should return whatever the string comparison returns (unequal in this case)
131-
t.is(element1.compare(element2), unequal)
131+
t.is(element1.compare(element2, 'full'), unequal)
132132

133133
// When values are equal
134134
const value3 = new StringRepresentation('test')
135135
const element3 = new ElementAccessor(5, value3)
136-
t.is(element1.compare(element3), strictlyEqual)
136+
t.is(element1.compare(element3, 'full'), strictlyEqual)
137137
})
138138

139139
test('ElementAccessor - serialize delegates to value serializeShallow if available', (t) => {
@@ -196,7 +196,7 @@ test('ElementAccessor - handles complex nesting', (t) => {
196196
const outer2 = new ElementAccessor(1, inner2)
197197

198198
// Deep comparison should work
199-
t.is(outer.compare(outer2), strictlyEqual)
199+
t.is(outer.compare(outer2, 'full'), strictlyEqual)
200200
})
201201

202202
test('ElementAccessor - finalFormat appends theme.element.after to formatter', (t) => {

src/accessors/test/iterator-value.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ test('compare returns unequal for non-IteratorValueAccessor', (t) => {
5757
},
5858
}
5959

60-
t.is(iteratorValue.compare(nonIteratorValue as unknown as ValueRepresentation), unequal)
60+
t.is(iteratorValue.compare(nonIteratorValue as unknown as ValueRepresentation, 'full'), unequal)
6161
})
6262

6363
test('compare returns unequal for different indices', (t) => {
@@ -66,7 +66,7 @@ test('compare returns unequal for different indices', (t) => {
6666
const iteratorValue1 = new IteratorValueAccessor(5, value)
6767
const iteratorValue2 = new IteratorValueAccessor(10, value)
6868

69-
t.is(iteratorValue1.compare(iteratorValue2), unequal)
69+
t.is(iteratorValue1.compare(iteratorValue2, 'full'), unequal)
7070
})
7171

7272
test('compare delegates to value comparison when indices match', (t) => {
@@ -77,12 +77,12 @@ test('compare delegates to value comparison when indices match', (t) => {
7777
const iteratorValue2 = new IteratorValueAccessor(5, value2)
7878

7979
// Should return whatever the string comparison returns (unequal in this case)
80-
t.is(iteratorValue1.compare(iteratorValue2), unequal)
80+
t.is(iteratorValue1.compare(iteratorValue2, 'full'), unequal)
8181

8282
// When values are equal
8383
const value3 = new StringRepresentation('test')
8484
const iteratorValue3 = new IteratorValueAccessor(5, value3)
85-
t.is(iteratorValue1.compare(iteratorValue3), strictlyEqual)
85+
t.is(iteratorValue1.compare(iteratorValue3, 'full'), strictlyEqual)
8686
})
8787

8888
// Test serialize
@@ -139,10 +139,10 @@ test('works with primitive values', (t) => {
139139

140140
// Comparison
141141
const iteratorValue2 = new IteratorValueAccessor(0, new NumberRepresentation(42))
142-
t.is(iteratorValue.compare(iteratorValue2), strictlyEqual)
142+
t.is(iteratorValue.compare(iteratorValue2, 'full'), strictlyEqual)
143143

144144
const iteratorValue3 = new IteratorValueAccessor(0, new NumberRepresentation(43))
145-
t.is(iteratorValue.compare(iteratorValue3), unequal)
145+
t.is(iteratorValue.compare(iteratorValue3, 'full'), unequal)
146146
})
147147

148148
test('handles nested iterator values', (t) => {
@@ -164,7 +164,7 @@ test('handles nested iterator values', (t) => {
164164
const outer2 = new IteratorValueAccessor(1, inner2)
165165

166166
// Deep comparison should work
167-
t.is(outer.compare(outer2), strictlyEqual)
167+
t.is(outer.compare(outer2, 'full'), strictlyEqual)
168168
})
169169

170170
// Test finalFormat

src/accessors/test/map-entry.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ test('compare returns unequal for non-MapEntryAccessor', (t) => {
9191
},
9292
}
9393

94-
t.is(mapEntry.compare(nonMapEntry as unknown as ValueRepresentation), unequal)
94+
t.is(mapEntry.compare(nonMapEntry as unknown as ValueRepresentation, 'full'), unequal)
9595
})
9696

9797
test('compare returns key comparison result when keys are not equal', (t) => {
@@ -106,7 +106,7 @@ test('compare returns key comparison result when keys are not equal', (t) => {
106106
const mapEntry2 = new MapEntryAccessor(context, key2, value2)
107107

108108
// Should return the result of comparing keys (unequal in this case)
109-
t.is(mapEntry1.compare(mapEntry2), unequal)
109+
t.is(mapEntry1.compare(mapEntry2, 'full'), unequal)
110110
})
111111

112112
test('compare returns value comparison result when keys are equal', (t) => {
@@ -122,14 +122,14 @@ test('compare returns value comparison result when keys are equal', (t) => {
122122
const mapEntry2 = new MapEntryAccessor(context, sameKey, value2)
123123

124124
// Keys are strictly equal, so should return result of value comparison
125-
t.is(mapEntry1.compare(mapEntry2), unequal)
125+
t.is(mapEntry1.compare(mapEntry2, 'full'), unequal)
126126

127127
// Try with equal values too
128128
const value3 = new StringRepresentation('value1')
129129
const mapEntry3 = new MapEntryAccessor(context, sameKey, value3)
130130

131131
// Both keys and values are strictly equal
132-
t.is(mapEntry1.compare(mapEntry3), strictlyEqual)
132+
t.is(mapEntry1.compare(mapEntry3, 'full'), strictlyEqual)
133133
})
134134

135135
test('compare handles strictlyEqual keys across contexts', (t) => {
@@ -147,7 +147,7 @@ test('compare handles strictlyEqual keys across contexts', (t) => {
147147

148148
// Keys should be strictly equal because they have the same content
149149
// Values are also strictly equal
150-
t.is(mapEntry1.compare(mapEntry2), strictlyEqual)
150+
t.is(mapEntry1.compare(mapEntry2, 'full'), strictlyEqual)
151151
})
152152

153153
test('compare handles possiblyEqual keys correctly', (t) => {
@@ -180,14 +180,14 @@ test('compare handles possiblyEqual keys correctly', (t) => {
180180
const mapEntry2 = new MapEntryAccessor(deserializationContext, key2!, value2)
181181

182182
// Keys are possiblyEqual but values are unequal
183-
t.is(mapEntry1.compare(mapEntry2), unequal)
183+
t.is(mapEntry1.compare(mapEntry2, 'full'), unequal)
184184

185185
// Same value content
186186
const value3 = new StringRepresentation('value1')
187187
const mapEntry3 = new MapEntryAccessor(deserializationContext, key2!, value3)
188188

189189
// Now with equal values, result should match the value comparison
190-
t.is(mapEntry1.compare(mapEntry3), strictlyEqual)
190+
t.is(mapEntry1.compare(mapEntry3, 'full'), strictlyEqual)
191191
})
192192

193193
// Test serialize

src/accessors/test/property.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ test('NamedPropertyAccessor - compare returns unequal for non-NamedPropertyAcces
4545
const value = new StringRepresentation('value')
4646
const property = new NamedPropertyAccessor(key, value)
4747

48-
t.is(property.compare(value), unequal)
48+
t.is(property.compare(value, 'full'), unequal)
4949
})
5050

5151
test('NamedPropertyAccessor - compare returns unequal for different keys', (t) => {
@@ -54,7 +54,7 @@ test('NamedPropertyAccessor - compare returns unequal for different keys', (t) =
5454
const property1 = new NamedPropertyAccessor('prop1', value)
5555
const property2 = new NamedPropertyAccessor('prop2', value)
5656

57-
t.is(property1.compare(property2), unequal)
57+
t.is(property1.compare(property2, 'full'), unequal)
5858
})
5959

6060
test('NamedPropertyAccessor - compare delegates to value comparison when keys match', (t) => {
@@ -66,12 +66,12 @@ test('NamedPropertyAccessor - compare delegates to value comparison when keys ma
6666
const property2 = new NamedPropertyAccessor(key, value2)
6767

6868
// Should return result of comparing values (unequal in this case)
69-
t.is(property1.compare(property2), unequal)
69+
t.is(property1.compare(property2, 'full'), unequal)
7070

7171
// When values are equal
7272
const value3 = new StringRepresentation('value1')
7373
const property3 = new NamedPropertyAccessor(key, value3)
74-
t.is(property1.compare(property3), strictlyEqual)
74+
t.is(property1.compare(property3, 'full'), strictlyEqual)
7575
})
7676

7777
test('NamedPropertyAccessor - serialize encodes key as string and delegates to value.serializeShallow', (t) => {
@@ -223,7 +223,7 @@ test('SymbolPropertyAccessor - compare returns unequal for non-SymbolPropertyAcc
223223

224224
const property = new SymbolPropertyAccessor(key, value)
225225

226-
t.is(property.compare(value), unequal)
226+
t.is(property.compare(value, 'full'), unequal)
227227
})
228228

229229
test('SymbolPropertyAccessor - compare returns unequal for different symbol keys', (t) => {
@@ -240,7 +240,7 @@ test('SymbolPropertyAccessor - compare returns unequal for different symbol keys
240240
const property1 = new SymbolPropertyAccessor(key1, value)
241241
const property2 = new SymbolPropertyAccessor(key2, value)
242242

243-
t.is(property1.compare(property2), unequal)
243+
t.is(property1.compare(property2, 'full'), unequal)
244244
})
245245

246246
test('SymbolPropertyAccessor - compare delegates to value comparison when symbol keys match', (t) => {
@@ -257,12 +257,12 @@ test('SymbolPropertyAccessor - compare delegates to value comparison when symbol
257257
const property2 = new SymbolPropertyAccessor(key, value2)
258258

259259
// Should return result of comparing values (unequal in this case)
260-
t.is(property1.compare(property2), unequal)
260+
t.is(property1.compare(property2, 'full'), unequal)
261261

262262
// When values are equal
263263
const value3 = new StringRepresentation('value1')
264264
const property3 = new SymbolPropertyAccessor(key, value3)
265-
t.is(property1.compare(property3), strictlyEqual)
265+
t.is(property1.compare(property3, 'full'), strictlyEqual)
266266
})
267267

268268
test('SymbolPropertyAccessor - serialize encodes key and returns partial', (t) => {
@@ -542,13 +542,14 @@ test('NamedPropertyGroup - iterator loads properties from DeserializationContext
542542

543543
// Find and verify prop1
544544
const foundProp1 = properties.find(
545-
(prop) => prop.compare(new NamedPropertyAccessor('prop1', new StringRepresentation('value1'))) === strictlyEqual,
545+
(prop) =>
546+
prop.compare(new NamedPropertyAccessor('prop1', new StringRepresentation('value1')), 'full') === strictlyEqual,
546547
)
547548
t.truthy(foundProp1, 'Should find property with key "prop1" and value "value1"')
548549

549550
// Find and verify prop2
550551
const foundProp2 = properties.find(
551-
(prop) => prop.compare(new NamedPropertyAccessor('prop2', new NumberRepresentation(42))) === strictlyEqual,
552+
(prop) => prop.compare(new NamedPropertyAccessor('prop2', new NumberRepresentation(42)), 'full') === strictlyEqual,
552553
)
553554
t.truthy(foundProp2, 'Should find property with key "prop2" and value 42')
554555
})

src/test/compare.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { mock } from 'node:test'
22
import test from 'ava'
33
import { compare, compareRepresentations } from '../compare.ts'
4-
import { comparable, deeplyEqual, unequal, strictlyEqual, type Comparison } from '../comparison.ts'
4+
import { comparable, deeplyEqual, unequal, strictlyEqual, type Comparison, type Mode } from '../comparison.ts'
55
import { finished, type SerializationResult } from '../serialization-result.ts'
66
import type {
77
AccessorFunctionality,
@@ -23,7 +23,7 @@ class MockValueRepresentation implements CommonRepresentation, DeepFunctionality
2323
this.children = children
2424
}
2525

26-
compare(): Comparison {
26+
compare(_other: ValueRepresentation, _mode: Mode): Comparison {
2727
return this.#compareResult
2828
}
2929

@@ -156,6 +156,14 @@ test('compare passes complex objects to compareDescriptors', (t) => {
156156
})
157157

158158
// Tests for compareDescriptors() function
159+
test('compareDescriptors mode defaults to "full"', (t) => {
160+
const lhs = new MockValueRepresentation(comparable)
161+
const spy = mock.method(lhs, 'compare')
162+
const rhs = new MockValueRepresentation(comparable)
163+
compareRepresentations(lhs, rhs)
164+
t.is(spy.mock.calls[0]?.arguments[1], 'full', 'Default mode should be "full"')
165+
})
166+
159167
test('compareDescriptors returns true when both value representations are deeply equal', (t) => {
160168
const lhs = new MockValueRepresentation(deeplyEqual)
161169
const rhs = new MockValueRepresentation(deeplyEqual)

0 commit comments

Comments
 (0)