unacy 0.6.0 → 0.8.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.
Files changed (40) hide show
  1. package/README.md +193 -47
  2. package/dist/converters.d.ts +28 -4
  3. package/dist/converters.d.ts.map +1 -1
  4. package/dist/errors.js.map +1 -1
  5. package/dist/index.d.ts +5 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/registry.d.ts +39 -41
  10. package/dist/registry.d.ts.map +1 -1
  11. package/dist/registry.js +58 -14
  12. package/dist/registry.js.map +1 -1
  13. package/dist/types.d.ts +159 -18
  14. package/dist/types.d.ts.map +1 -1
  15. package/dist/types.js.map +1 -1
  16. package/dist/utils/validation.d.ts +106 -0
  17. package/dist/utils/validation.d.ts.map +1 -1
  18. package/dist/utils/validation.js +317 -0
  19. package/dist/utils/validation.js.map +1 -1
  20. package/package.json +16 -8
  21. package/dist/__tests__/converters.test.d.ts +0 -2
  22. package/dist/__tests__/converters.test.d.ts.map +0 -1
  23. package/dist/__tests__/converters.test.js +0 -128
  24. package/dist/__tests__/converters.test.js.map +0 -1
  25. package/dist/__tests__/errors.test.d.ts +0 -2
  26. package/dist/__tests__/errors.test.d.ts.map +0 -1
  27. package/dist/__tests__/errors.test.js +0 -93
  28. package/dist/__tests__/errors.test.js.map +0 -1
  29. package/dist/__tests__/formatters.test.d.ts +0 -2
  30. package/dist/__tests__/formatters.test.d.ts.map +0 -1
  31. package/dist/__tests__/formatters.test.js +0 -244
  32. package/dist/__tests__/formatters.test.js.map +0 -1
  33. package/dist/__tests__/registry.test.d.ts +0 -2
  34. package/dist/__tests__/registry.test.d.ts.map +0 -1
  35. package/dist/__tests__/registry.test.js +0 -403
  36. package/dist/__tests__/registry.test.js.map +0 -1
  37. package/dist/__tests__/types.test.d.ts +0 -2
  38. package/dist/__tests__/types.test.d.ts.map +0 -1
  39. package/dist/__tests__/types.test.js +0 -115
  40. package/dist/__tests__/types.test.js.map +0 -1
@@ -1,244 +0,0 @@
1
- import { describe, it, expect, expectTypeOf } from 'vitest';
2
- import { ParseError } from '../errors.js';
3
- import { z } from 'zod';
4
- describe('Formatter Type', () => {
5
- it('formatter converts tagged value to string', () => {
6
- const formatISO = (date) => date.toISOString();
7
- const now = new Date('2026-01-06T12:00:00.000Z');
8
- const str = formatISO(now);
9
- expect(typeof str).toBe('string');
10
- expect(str).toBe('2026-01-06T12:00:00.000Z');
11
- });
12
- it('formatter maintains format identity through transformation', () => {
13
- const formatHex = (color) => color.toUpperCase();
14
- const color = '#ff5733';
15
- const formatted = formatHex(color);
16
- expect(formatted).toBe('#FF5733');
17
- expectTypeOf(formatted).toEqualTypeOf();
18
- });
19
- it('formatter works with different base types', () => {
20
- const formatTimestamp = (ts) => String(ts);
21
- const timestamp = Date.now();
22
- const str = formatTimestamp(timestamp);
23
- expect(typeof str).toBe('string');
24
- expect(str).toMatch(/^\d+$/);
25
- });
26
- });
27
- describe('Parser Type', () => {
28
- it('parser converts string to tagged value with Zod validation', () => {
29
- const parseISO = (input) => {
30
- const schema = z.string().datetime();
31
- const validated = schema.parse(input);
32
- return new Date(validated);
33
- };
34
- const str = '2026-01-06T12:00:00.000Z';
35
- const date = parseISO(str);
36
- expectTypeOf(date).toEqualTypeOf();
37
- expect(date).toBeInstanceOf(Date);
38
- expect(date.toISOString()).toBe(str);
39
- });
40
- it('invalid string input throws ParseError with context', () => {
41
- const parseHex = (input) => {
42
- const schema = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
43
- try {
44
- return schema.parse(input);
45
- }
46
- catch {
47
- throw new ParseError('HexColor', input, 'Expected #RRGGBB format');
48
- }
49
- };
50
- expect(() => {
51
- parseHex('not-a-color');
52
- }).toThrow(ParseError);
53
- try {
54
- parseHex('invalid');
55
- }
56
- catch (err) {
57
- expect(err).toBeInstanceOf(ParseError);
58
- if (err instanceof ParseError) {
59
- expect(err.format).toBe('HexColor');
60
- expect(err.input).toBe('invalid');
61
- expect(err.reason).toBe('Expected #RRGGBB format');
62
- }
63
- }
64
- });
65
- it('parser rejects input before tagging (no invalid tagged values)', () => {
66
- const parsePositive = (input) => {
67
- const schema = z
68
- .string()
69
- .transform(parseFloat)
70
- .refine((n) => n > 0);
71
- try {
72
- return schema.parse(input);
73
- }
74
- catch {
75
- throw new ParseError('Positive', input, 'Must be a positive number');
76
- }
77
- };
78
- expect(() => {
79
- parsePositive('-5');
80
- }).toThrow(ParseError);
81
- expect(() => {
82
- parsePositive('not-a-number');
83
- }).toThrow(ParseError);
84
- // Valid input should work
85
- const result = parsePositive('10');
86
- expect(result).toBe(10);
87
- });
88
- it('handles empty input', () => {
89
- const parseNonEmpty = (input) => {
90
- if (input === '') {
91
- throw new ParseError('NonEmpty', input, 'Cannot be empty');
92
- }
93
- return input;
94
- };
95
- expect(() => {
96
- parseNonEmpty('');
97
- }).toThrow(ParseError);
98
- try {
99
- parseNonEmpty('');
100
- }
101
- catch (err) {
102
- if (err instanceof ParseError) {
103
- expect(err.message).toContain('""');
104
- }
105
- }
106
- });
107
- it('compile-time: wrong format type to parser causes error', () => {
108
- const parseISO = (input) => {
109
- return new Date(input);
110
- };
111
- const str = '2026-01-06T12:00:00.000Z';
112
- const date = parseISO(str);
113
- // @ts-expect-error - Cannot assign ISO8601 to HexColor
114
- const color = date;
115
- expect(color).toBeDefined();
116
- });
117
- });
118
- describe('FormatterParser Type', () => {
119
- it('round-trip (format then parse) produces equivalent value', () => {
120
- const iso8601 = {
121
- format: (date) => date.toISOString(),
122
- parse: (input) => {
123
- const schema = z.string().datetime();
124
- const validated = schema.parse(input);
125
- return new Date(validated);
126
- }
127
- };
128
- const original = new Date('2026-01-06T12:00:00.000Z');
129
- const formatted = iso8601.format(original);
130
- const parsed = iso8601.parse(formatted);
131
- expect(parsed.getTime()).toBe(original.getTime());
132
- });
133
- it('integration test: full formatter/parser pair for ISO8601 dates', () => {
134
- const iso8601 = {
135
- format: (date) => date.toISOString(),
136
- parse: (input) => {
137
- const schema = z.string().datetime();
138
- try {
139
- const validated = schema.parse(input);
140
- return new Date(validated);
141
- }
142
- catch {
143
- throw new ParseError('ISO8601', input, 'Invalid ISO8601 date format');
144
- }
145
- }
146
- };
147
- // Test formatting
148
- const date = new Date('2026-01-06T12:34:56.789Z');
149
- const str = iso8601.format(date);
150
- expect(str).toBe('2026-01-06T12:34:56.789Z');
151
- // Test parsing
152
- const parsed = iso8601.parse('2026-12-31T23:59:59.999Z');
153
- expect(parsed).toBeInstanceOf(Date);
154
- expect(parsed.toISOString()).toBe('2026-12-31T23:59:59.999Z');
155
- // Test round-trip
156
- const roundTrip = iso8601.parse(iso8601.format(date));
157
- expect(roundTrip.getTime()).toBe(date.getTime());
158
- // Test error handling
159
- expect(() => {
160
- iso8601.parse('invalid-date');
161
- }).toThrow(ParseError);
162
- });
163
- it('works with different format types', () => {
164
- const hexColor = {
165
- format: (color) => color.toLowerCase(),
166
- parse: (input) => {
167
- const schema = z.string().regex(/^#[0-9a-fA-F]{6}$/);
168
- try {
169
- return schema.parse(input);
170
- }
171
- catch {
172
- throw new ParseError('HexColor', input, 'Expected #RRGGBB format');
173
- }
174
- }
175
- };
176
- const color = '#FF5733';
177
- const formatted = hexColor.format(color);
178
- expect(formatted).toBe('#ff5733');
179
- const parsed = hexColor.parse('#00FF00');
180
- expectTypeOf(parsed).toEqualTypeOf();
181
- expect(parsed).toBe('#00FF00');
182
- // Round-trip with case normalization
183
- const original = '#ABCDEF';
184
- const roundTrip = hexColor.parse(hexColor.format(original));
185
- expect(roundTrip.toLowerCase()).toBe(original.toLowerCase());
186
- });
187
- it('timestamp formatter/parser preserves value', () => {
188
- const unixTimestamp = {
189
- format: (ts) => String(ts),
190
- parse: (input) => {
191
- const schema = z
192
- .string()
193
- .regex(/^\d+$/)
194
- .transform((s) => parseInt(s, 10));
195
- try {
196
- return schema.parse(input);
197
- }
198
- catch {
199
- throw new ParseError('UnixTimestamp', input, 'Expected Unix timestamp');
200
- }
201
- }
202
- };
203
- const timestamp = Date.now();
204
- const str = unixTimestamp.format(timestamp);
205
- const parsed = unixTimestamp.parse(str);
206
- expect(parsed).toBe(timestamp);
207
- });
208
- });
209
- describe('Formatter/Parser Examples', () => {
210
- it('YYYY-MM-DD format with validation', () => {
211
- const yyyymmdd = {
212
- format: (dateStr) => dateStr,
213
- parse: (input) => {
214
- const schema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
215
- try {
216
- const validated = schema.parse(input);
217
- // Additional validation: check if it's a valid date
218
- const parts = validated.split('-');
219
- const year = parseInt(parts[0], 10);
220
- const month = parseInt(parts[1], 10);
221
- const day = parseInt(parts[2], 10);
222
- const date = new Date(year, month - 1, day);
223
- if (date.getFullYear() !== year ||
224
- date.getMonth() !== month - 1 ||
225
- date.getDate() !== day) {
226
- throw new Error('Invalid date');
227
- }
228
- return validated;
229
- }
230
- catch {
231
- throw new ParseError('YYYY-MM-DD', input, 'Invalid date format');
232
- }
233
- }
234
- };
235
- // Valid dates
236
- expect(yyyymmdd.parse('2026-01-06')).toBe('2026-01-06');
237
- expect(yyyymmdd.parse('2026-12-31')).toBe('2026-12-31');
238
- // Invalid dates
239
- expect(() => yyyymmdd.parse('2026-13-01')).toThrow(ParseError); // Invalid month
240
- expect(() => yyyymmdd.parse('2026-02-30')).toThrow(ParseError); // Invalid day
241
- expect(() => yyyymmdd.parse('not-a-date')).toThrow(ParseError);
242
- });
243
- });
244
- //# sourceMappingURL=formatters.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"formatters.test.js","sourceRoot":"","sources":["../../src/__tests__/formatters.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=registry.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/registry.test.ts"],"names":[],"mappings":""}
@@ -1,403 +0,0 @@
1
- import { describe, it, expect, expectTypeOf } from 'vitest';
2
- import { createRegistry } from '../registry.js';
3
- import { CycleError, MaxDepthError, ConversionError } from '../errors.js';
4
- const getConverter = (registry, from, to) => registry.getConverter(from, to);
5
- const convert = (registry, value, from) => registry.convert(value, from);
6
- describe('Registry - Basic Operations', () => {
7
- it('register and retrieve direct converter', () => {
8
- const registry = createRegistry().register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32));
9
- const converter = getConverter(registry, 'Celsius', 'Fahrenheit');
10
- expect(converter).toBeDefined();
11
- if (converter) {
12
- const result = converter(0);
13
- expect(result).toBe(32);
14
- }
15
- });
16
- it('registerBidirectional registers both directions', () => {
17
- const registry = createRegistry().register('meters', 'kilometers', {
18
- to: (m) => (m / 1000),
19
- from: (km) => (km * 1000)
20
- });
21
- const m2km = getConverter(registry, 'meters', 'kilometers');
22
- const km2m = getConverter(registry, 'kilometers', 'meters');
23
- expect(m2km).toBeDefined();
24
- expect(km2m).toBeDefined();
25
- if (m2km && km2m) {
26
- expect(m2km(1000)).toBe(1);
27
- expect(km2m(1)).toBe(1000);
28
- }
29
- });
30
- it('getConverter finds direct converter in O(1)', () => {
31
- const registry = createRegistry()
32
- .register('A', 'B', (a) => (a * 2))
33
- .register('B', 'C', (b) => (b * 3));
34
- const startTime = performance.now();
35
- const converter = getConverter(registry, 'A', 'B');
36
- const endTime = performance.now();
37
- expect(converter).toBeDefined();
38
- // O(1) lookup should be fast (< 1ms even in slow environments)
39
- expect(endTime - startTime).toBeLessThan(5);
40
- });
41
- });
42
- describe('Registry - Multi-Hop Composition', () => {
43
- it('auto-compose 2-hop conversion (A→B→C)', () => {
44
- const registry = createRegistry()
45
- .register('A', 'B', (a) => (a * 2))
46
- .register('B', 'C', (b) => (b * 3));
47
- const converter = getConverter(registry, 'A', 'C');
48
- expect(converter).toBeDefined();
49
- if (converter) {
50
- // A→B→C: 10 * 2 = 20, 20 * 3 = 60
51
- expect(converter(10)).toBe(60);
52
- }
53
- });
54
- it('auto-compose 3-hop conversion (A→B→C→D)', () => {
55
- const registry = createRegistry()
56
- .register('A', 'B', (a) => (a * 2))
57
- .register('B', 'C', (b) => (b * 3))
58
- .register('C', 'D', (c) => (c * 5));
59
- const converter = getConverter(registry, 'A', 'D');
60
- expect(converter).toBeDefined();
61
- if (converter) {
62
- // A→B→C→D: 10 * 2 * 3 * 5 = 300
63
- expect(converter(10)).toBe(300);
64
- }
65
- });
66
- it('integration test: 3-unit distance conversion (m→km→mi)', () => {
67
- const registry = createRegistry()
68
- .register('meters', 'kilometers', {
69
- to: (m) => (m / 1000),
70
- from: (km) => (km * 1000)
71
- })
72
- .register('kilometers', 'miles', {
73
- to: (km) => (km * 0.621371),
74
- from: (mi) => (mi / 0.621371)
75
- });
76
- const converter = getConverter(registry, 'meters', 'miles');
77
- expect(converter).toBeDefined();
78
- if (converter) {
79
- const meters = 5000;
80
- const miles = converter(meters);
81
- // 5000m → 5km → 3.106855mi
82
- expect(miles).toBeCloseTo(3.106855, 5);
83
- }
84
- });
85
- it('chooses shortest path when multiple paths exist', () => {
86
- // Create diamond shape: A→B→D and A→C→D
87
- // But also add direct A→D
88
- const registry = createRegistry()
89
- .register('A', 'B', (a) => (a + 100)) // Long path
90
- .register('B', 'D', (b) => (b + 100))
91
- .register('A', 'C', (a) => (a + 200)) // Long path
92
- .register('C', 'D', (c) => (c + 200))
93
- .register('A', 'D', (a) => (a * 10)); // Direct (shortest)
94
- const converter = getConverter(registry, 'A', 'D');
95
- if (converter) {
96
- // Should use direct path: 5 * 10 = 50
97
- expect(converter(5)).toBe(50);
98
- }
99
- });
100
- });
101
- describe('Registry - Error Handling', () => {
102
- it('cycle detection throws CycleError with path', () => {
103
- // Create cycle: A→B→C→A
104
- const registry = createRegistry()
105
- .register('A', 'B', (a) => (a * 2))
106
- .register('B', 'C', (b) => (b * 3))
107
- .register('C', 'A', (c) => (c * 5));
108
- // Trying to convert from C to A would complete a cycle
109
- // But the actual cycle is detected when finding a path
110
- // Let me test a different scenario: trying to get a converter that would require going in a circle
111
- // Since we register C→A, we can actually get A to C via A→B→C
112
- // But trying to get C to C should trigger cycle detection
113
- expect(() => {
114
- getConverter(registry, 'C', 'C'); // Cast needed due to type constraint
115
- }).toThrow(CycleError);
116
- });
117
- it('max depth (>5 hops) throws MaxDepthError', () => {
118
- // Create chain: A→B→C→D→E→F→G (6 hops)
119
- const registry = createRegistry()
120
- .register('A', 'B', (a) => (a * 2))
121
- .register('B', 'C', (b) => (b * 2))
122
- .register('C', 'D', (c) => (c * 2))
123
- .register('D', 'E', (d) => (d * 2))
124
- .register('E', 'F', (e) => (e * 2))
125
- .register('F', 'G', (f) => (f * 2));
126
- expect(() => {
127
- getConverter(registry, 'A', 'G');
128
- }).toThrow(MaxDepthError);
129
- });
130
- it('missing converter path throws ConversionError', () => {
131
- const registry = createRegistry().register('A', 'B', (a) => (a * 2));
132
- // No path from A to C
133
- const result = getConverter(registry, 'A', 'C');
134
- expect(result).toBeUndefined();
135
- });
136
- });
137
- describe('Registry - Fluent API', () => {
138
- it('compile-time: wrong unit to convert() causes error', () => {
139
- const registry = createRegistry().register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32));
140
- const temp = 25;
141
- // Valid conversion
142
- const fahrenheit = convert(registry, temp, 'Celsius').to('Fahrenheit');
143
- expect(fahrenheit).toBe(77);
144
- // Type safety test: This tests that wrong units are caught at compile-time
145
- // We can't easily test this at runtime, so we document the expected behavior
146
- });
147
- it('fluent API performs direct conversion', () => {
148
- const registry = createRegistry().register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32));
149
- const temp = 0;
150
- const result = convert(registry, temp, 'Celsius').to('Fahrenheit');
151
- expect(result).toBe(32);
152
- // Type is WithUnits<any, 'Fahrenheit'> which is compatible with Fahrenheit
153
- });
154
- it('fluent API performs multi-hop conversion', () => {
155
- const registry = createRegistry()
156
- .register('Celsius', 'Kelvin', (c) => (c + 273.15))
157
- .register('Kelvin', 'Fahrenheit', (k) => (((k - 273.15) * 9) / 5 + 32));
158
- const temp = 0;
159
- const fahrenheit = convert(registry, temp, 'Celsius').to('Fahrenheit');
160
- expect(fahrenheit).toBeCloseTo(32, 5);
161
- });
162
- it('fluent API throws error for missing path', () => {
163
- const registry = createRegistry().register('A', 'B', (a) => (a * 2));
164
- expect(() => {
165
- convert(registry, 10, 'A').to('C');
166
- }).toThrow(ConversionError);
167
- });
168
- });
169
- describe('Registry - Immutability', () => {
170
- it('register returns new registry instance', () => {
171
- const registry1 = createRegistry();
172
- const registry2 = registry1.register('A', 'B', (a) => (a * 2));
173
- expect(registry1).not.toBe(registry2);
174
- });
175
- it('original registry unchanged after register', () => {
176
- const registry1 = createRegistry();
177
- const registry2 = registry1.register('A', 'B', (a) => (a * 2));
178
- // registry1 should not have the converter
179
- expect(getConverter(registry1, 'A', 'B')).toBeUndefined();
180
- // registry2 should have the converter
181
- expect(getConverter(registry2, 'A', 'B')).toBeDefined();
182
- });
183
- });
184
- describe('Registry - Performance', () => {
185
- it('caches composed paths for repeated conversions', () => {
186
- const registry = createRegistry()
187
- .register('A', 'B', (a) => (a * 2))
188
- .register('B', 'C', (b) => (b * 3));
189
- // First call - may be slower due to BFS
190
- const start1 = performance.now();
191
- const converter1 = getConverter(registry, 'A', 'C');
192
- const end1 = performance.now();
193
- const time1 = end1 - start1;
194
- // Second call - should be faster (cached)
195
- const start2 = performance.now();
196
- const converter2 = getConverter(registry, 'A', 'C');
197
- const end2 = performance.now();
198
- const time2 = end2 - start2;
199
- expect(converter1).toBeDefined();
200
- expect(converter2).toBeDefined();
201
- // Second call should be as fast or faster (cached)
202
- expect(time2).toBeLessThanOrEqual(time1 * 2); // Allow some variance
203
- });
204
- });
205
- describe('Registry - Unit Accessor API', () => {
206
- it('provides unit-based accessor API', () => {
207
- const registry = createRegistry().register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32));
208
- const temp = 0;
209
- // Use the unit accessor API: registry.celsius.to.fahrenheit(value)
210
- const result = registry.Celsius.to.Fahrenheit(temp);
211
- expect(result).toBe(32);
212
- // Type checking note: result is 'any' due to registry cast, but runtime value is correct
213
- });
214
- it('unit accessor is callable to create branded values', () => {
215
- const registry = createRegistry().register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32));
216
- // Call the unit accessor as a function to brand a value
217
- const temp = registry.Celsius(25);
218
- // Verify it's treated as a branded Celsius value
219
- expect(temp).toBe(25);
220
- // And can be converted
221
- const fahrenheit = registry.Celsius.to.Fahrenheit(temp);
222
- expect(fahrenheit).toBe(77);
223
- });
224
- it('callable unit accessor works for unit-centric workflow', () => {
225
- const registry = createRegistry().register('meters', 'kilometers', {
226
- to: (m) => (m / 1000),
227
- from: (km) => (km * 1000)
228
- });
229
- // Create branded values by calling the unit accessor
230
- const distance = registry.meters(5000);
231
- const km = registry.meters.to.kilometers(distance);
232
- expect(km).toBe(5);
233
- });
234
- it('unit accessor API works with bidirectional converters', () => {
235
- const registry = createRegistry().register('meters', 'kilometers', {
236
- to: (m) => (m / 1000),
237
- from: (km) => (km * 1000)
238
- });
239
- const meters = 5000;
240
- const km = 5;
241
- // Both directions should work
242
- const toKm = registry.meters.to.kilometers(meters);
243
- const toM = registry.kilometers.to.meters(km);
244
- expect(toKm).toBe(5);
245
- expect(toM).toBe(5000);
246
- });
247
- it('unit accessor API works with multi-hop conversions', () => {
248
- const registry = createRegistry()
249
- .register('meters', 'kilometers', {
250
- to: (m) => (m / 1000),
251
- from: (km) => (km * 1000)
252
- })
253
- .register('kilometers', 'miles', {
254
- to: (km) => (km * 0.621371),
255
- from: (mi) => (mi / 0.621371)
256
- });
257
- const meters = 5000;
258
- // Should auto-compose: meters → kilometers → miles
259
- const miles = registry.meters.to.miles(meters);
260
- expect(miles).toBeCloseTo(3.106855, 5);
261
- });
262
- it('unit accessor API throws error when no converter exists', () => {
263
- const registry = createRegistry().register('A', 'B', (a) => (a * 2));
264
- // Try to convert from A to C (C not in registry at all)
265
- expect(() => {
266
- registry.A.to.C(10);
267
- }).toThrow(ConversionError);
268
- });
269
- });
270
- describe('Registry - Metadata Support', () => {
271
- it('addMetadata attaches metadata to a unit', () => {
272
- const registry = createRegistry()
273
- .register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32))
274
- .Celsius.addMetadata({
275
- abbreviation: '°C',
276
- format: '${value}°C',
277
- description: 'Temperature in Celsius'
278
- });
279
- expect(registry.Celsius.abbreviation).toBe('°C');
280
- expect(registry.Celsius.format).toBe('${value}°C');
281
- expect(registry.Celsius.description).toBe('Temperature in Celsius');
282
- });
283
- it('metadata properties are accessible on unit accessors', () => {
284
- const registry = createRegistry()
285
- .register('meters', 'kilometers', (m) => (m / 1000))
286
- .meters.addMetadata({ abbreviation: 'm', symbol: 'm' });
287
- expect(registry.meters.abbreviation).toBe('m');
288
- expect(registry.meters.symbol).toBe('m');
289
- });
290
- it('addMetadata supports arbitrary custom properties', () => {
291
- const registry = createRegistry()
292
- .register('Kelvin', 'Celsius', (k) => (k - 273.15))
293
- .Kelvin.addMetadata({
294
- abbreviation: 'K',
295
- customProp: 'custom value',
296
- numericProp: 42
297
- });
298
- expect(registry.Kelvin.abbreviation).toBe('K');
299
- expect(registry.Kelvin.customProp).toBe('custom value');
300
- expect(registry.Kelvin.numericProp).toBe(42);
301
- });
302
- it('metadata persists across register operations', () => {
303
- const registry = createRegistry()
304
- .register('A', 'B', (a) => (a * 2))
305
- .A.addMetadata({ abbreviation: 'A' })
306
- .register('B', 'C', (b) => (b * 3));
307
- expect(registry.A.abbreviation).toBe('A');
308
- });
309
- it('addMetadata can update existing metadata', () => {
310
- const registry = createRegistry()
311
- .register('meters', 'feet', (m) => (m * 3.28084))
312
- .meters.addMetadata({ abbreviation: 'm' })
313
- .meters.addMetadata({ description: 'Length in meters' });
314
- expect(registry.meters.abbreviation).toBe('m');
315
- expect(registry.meters.description).toBe('Length in meters');
316
- });
317
- it('addMetadata overwrites existing properties', () => {
318
- const registry = createRegistry()
319
- .register('grams', 'kilograms', (g) => (g / 1000))
320
- .grams.addMetadata({ abbreviation: 'g' })
321
- .grams.addMetadata({ abbreviation: 'gram' });
322
- expect(registry.grams.abbreviation).toBe('gram');
323
- });
324
- it('multiple units can have independent metadata', () => {
325
- const registry = createRegistry()
326
- .register('Celsius', 'Fahrenheit', (c) => ((c * 9) / 5 + 32))
327
- .register('Fahrenheit', 'Celsius', (f) => (((f - 32) * 5) / 9))
328
- .Celsius.addMetadata({ abbreviation: '°C' })
329
- .Fahrenheit.addMetadata({ abbreviation: '°F' });
330
- expect(registry.Celsius.abbreviation).toBe('°C');
331
- expect(registry.Fahrenheit.abbreviation).toBe('°F');
332
- });
333
- it('metadata returns undefined for non-existent properties', () => {
334
- const registry = createRegistry()
335
- .register('meters', 'feet', (m) => (m * 3.28084))
336
- .meters.addMetadata({ abbreviation: 'm' });
337
- expect(registry.meters.abbreviation).toBe('m');
338
- expect(registry.meters.nonExistent).toBeUndefined();
339
- });
340
- it('addMetadata returns new registry instance (immutable)', () => {
341
- const registry1 = createRegistry().register('A', 'B', (a) => (a * 2));
342
- const registry2 = registry1.A.addMetadata({ abbreviation: 'A' });
343
- expect(registry1.A.abbreviation).toBeUndefined();
344
- expect(registry2.A.abbreviation).toBe('A');
345
- });
346
- });
347
- describe('Registry - Unit Accessor Registration', () => {
348
- it('register method on unit accessor registers converter', () => {
349
- const registry = createRegistry().Celsius.register('Fahrenheit', (c) => ((c * 9) / 5 + 32));
350
- const converter = getConverter(registry, 'Celsius', 'Fahrenheit');
351
- expect(converter).toBeDefined();
352
- if (converter) {
353
- expect(converter(0)).toBe(32);
354
- }
355
- });
356
- it('unit accessor register supports bidirectional converters', () => {
357
- const registry = createRegistry().meters.register('kilometers', {
358
- to: (m) => (m / 1000),
359
- from: (km) => (km * 1000)
360
- });
361
- const m2km = getConverter(registry, 'meters', 'kilometers');
362
- const km2m = getConverter(registry, 'kilometers', 'meters');
363
- expect(m2km).toBeDefined();
364
- expect(km2m).toBeDefined();
365
- if (m2km && km2m) {
366
- expect(m2km(1000)).toBe(1);
367
- expect(km2m(1)).toBe(1000);
368
- }
369
- });
370
- it('unit accessor register can be chained', () => {
371
- const registry = createRegistry()
372
- .Celsius.register('Fahrenheit', (c) => ((c * 9) / 5 + 32))
373
- .Fahrenheit.register('Kelvin', (f) => ((f - 32) * (5 / 9) + 273.15));
374
- const c2f = getConverter(registry, 'Celsius', 'Fahrenheit');
375
- const f2k = getConverter(registry, 'Fahrenheit', 'Kelvin');
376
- expect(c2f).toBeDefined();
377
- expect(f2k).toBeDefined();
378
- });
379
- it('unit accessor register preserves existing converters', () => {
380
- const registry = createRegistry()
381
- .register('A', 'B', (a) => (a * 2))
382
- .C.register('D', (c) => (c * 3));
383
- const ab = getConverter(registry, 'A', 'B');
384
- const cd = getConverter(registry, 'C', 'D');
385
- expect(ab).toBeDefined();
386
- expect(cd).toBeDefined();
387
- });
388
- it('unit accessor register works with unit accessor API', () => {
389
- const registry = createRegistry().Celsius.register('Fahrenheit', (c) => ((c * 9) / 5 + 32));
390
- const temp = 0;
391
- const result = registry.Celsius.to.Fahrenheit(temp);
392
- expect(result).toBe(32);
393
- });
394
- it('unit accessor register and addMetadata work together', () => {
395
- const registry = createRegistry()
396
- .Celsius.addMetadata({ abbreviation: '°C' })
397
- .Celsius.register('Fahrenheit', (c) => ((c * 9) / 5 + 32));
398
- expect(registry.Celsius.abbreviation).toBe('°C');
399
- const converter = getConverter(registry, 'Celsius', 'Fahrenheit');
400
- expect(converter).toBeDefined();
401
- });
402
- });
403
- //# sourceMappingURL=registry.test.js.map