|
1 | 1 | import type { Field } from '../src/field/type' |
2 | 2 | import { describe, expect, it } from '@jest/globals' |
3 | | -import { getField } from '../src/utils' |
| 3 | +import { convertKBToMB, deepMergeSchemas, getField } from '../src/utils' |
4 | 4 |
|
5 | 5 | describe('getField', () => { |
6 | 6 | const mockFields: Field[] = [ |
@@ -111,3 +111,182 @@ describe('getField', () => { |
111 | 111 | expect(field?.name).toBe('level3') |
112 | 112 | }) |
113 | 113 | }) |
| 114 | + |
| 115 | +describe('convertKBToMB', () => { |
| 116 | + it('should return 0 when given 0', () => { |
| 117 | + expect(convertKBToMB(0)).toBe(0) |
| 118 | + }) |
| 119 | + |
| 120 | + it('should convert KB to MB', () => { |
| 121 | + expect(convertKBToMB(1024)).toBe(1) |
| 122 | + expect(convertKBToMB(2048)).toBe(2) |
| 123 | + }) |
| 124 | + |
| 125 | + it('should round the result to 2 decimal places', () => { |
| 126 | + // 1505 / 1024 = 1.469726562 -> 1.46 |
| 127 | + expect(convertKBToMB(1505)).toBe(1.47) |
| 128 | + // 1590 / 1024 = 1.552734375 -> 1.55 |
| 129 | + expect(convertKBToMB(1590)).toBe(1.55) |
| 130 | + }) |
| 131 | + |
| 132 | + it('should handle values smaller than 1 MB', () => { |
| 133 | + // 512 / 1024 = 0.5 |
| 134 | + expect(convertKBToMB(512)).toBe(0.5) |
| 135 | + }) |
| 136 | +}) |
| 137 | + |
| 138 | +describe('deepMergeSchemas', () => { |
| 139 | + it('should do nothing when either schema is missing', () => { |
| 140 | + const schema1 = { type: 'string' } |
| 141 | + expect(() => deepMergeSchemas(undefined, schema1)).not.toThrow() |
| 142 | + expect(() => deepMergeSchemas(schema1, undefined)).not.toThrow() |
| 143 | + expect(schema1).toEqual({ type: 'string' }) |
| 144 | + }) |
| 145 | + |
| 146 | + it('should return early without mutating when a schema is a truthy non-object', () => { |
| 147 | + const schema1: Record<string, any> = { type: 'string' } |
| 148 | + expect(() => deepMergeSchemas(42 as any, schema1)).not.toThrow() |
| 149 | + expect(() => deepMergeSchemas(schema1, 'not-an-object' as any)).not.toThrow() |
| 150 | + expect(schema1).toEqual({ type: 'string' }) |
| 151 | + }) |
| 152 | + |
| 153 | + it('should copy over properties that only exist in schema2', () => { |
| 154 | + const schema1: Record<string, any> = { type: 'string' } |
| 155 | + deepMergeSchemas(schema1, { title: 'Name' }) |
| 156 | + expect(schema1).toEqual({ type: 'string', title: 'Name' }) |
| 157 | + }) |
| 158 | + |
| 159 | + it('should overwrite primitive values that differ', () => { |
| 160 | + const schema1: Record<string, any> = { title: 'Old' } |
| 161 | + deepMergeSchemas(schema1, { title: 'New' }) |
| 162 | + expect(schema1.title).toBe('New') |
| 163 | + }) |
| 164 | + |
| 165 | + it('should merge nested objects recursively', () => { |
| 166 | + const schema1: Record<string, any> = { |
| 167 | + properties: { name: { type: 'string' } }, |
| 168 | + } |
| 169 | + deepMergeSchemas(schema1, { |
| 170 | + properties: { age: { type: 'number' } }, |
| 171 | + }) |
| 172 | + expect(schema1.properties).toEqual({ |
| 173 | + name: { type: 'string' }, |
| 174 | + age: { type: 'number' }, |
| 175 | + }) |
| 176 | + }) |
| 177 | + |
| 178 | + it('should assign an object when the target value is not an object', () => { |
| 179 | + const schema1: Record<string, any> = { meta: 'string' } |
| 180 | + deepMergeSchemas(schema1, { meta: { nested: true } }) |
| 181 | + expect(schema1.meta).toEqual({ nested: true }) |
| 182 | + }) |
| 183 | + |
| 184 | + it('should skip if/then/else properties', () => { |
| 185 | + const schema1: Record<string, any> = { type: 'object' } |
| 186 | + deepMergeSchemas(schema1, { |
| 187 | + if: { properties: { a: { const: 1 } } }, |
| 188 | + then: { required: ['b'] }, |
| 189 | + else: { required: ['c'] }, |
| 190 | + }) |
| 191 | + expect(schema1).toEqual({ type: 'object' }) |
| 192 | + }) |
| 193 | + |
| 194 | + it('should replace options arrays instead of merging them', () => { |
| 195 | + const schema1: Record<string, any> = { |
| 196 | + options: [{ value: 'a' }, { value: 'b' }], |
| 197 | + } |
| 198 | + deepMergeSchemas(schema1, { |
| 199 | + options: [{ value: 'c' }], |
| 200 | + }) |
| 201 | + expect(schema1.options).toEqual([{ value: 'c' }]) |
| 202 | + }) |
| 203 | + |
| 204 | + it('should replace enum arrays instead of merging them', () => { |
| 205 | + const schema1: Record<string, any> = { enum: ['a', 'b'] } |
| 206 | + deepMergeSchemas(schema1, { enum: ['c', 'd'] }) |
| 207 | + expect(schema1.enum).toEqual(['c', 'd']) |
| 208 | + }) |
| 209 | + |
| 210 | + it('should only add new elements to the required array', () => { |
| 211 | + const schema1: Record<string, any> = { required: ['a', 'b'] } |
| 212 | + deepMergeSchemas(schema1, { required: ['b', 'c'] }) |
| 213 | + expect(schema1.required).toEqual(['a', 'b', 'c']) |
| 214 | + }) |
| 215 | + |
| 216 | + it('should replace primitive arrays, not merge by index', () => { |
| 217 | + const schema1: Record<string, any> = { enum: ['a', 'b'] } |
| 218 | + deepMergeSchemas(schema1, { enum: ['c', 'd'] }) |
| 219 | + expect(schema1.enum).toEqual(['c', 'd']) |
| 220 | + }) |
| 221 | + |
| 222 | + it('should replace the whole options array rather than merging it by index', () => { |
| 223 | + const schema1: Record<string, any> = { |
| 224 | + options: [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }], |
| 225 | + } |
| 226 | + const incomingOptions = [{ value: 'c', label: 'C' }] |
| 227 | + deepMergeSchemas(schema1, { |
| 228 | + options: incomingOptions, |
| 229 | + }) |
| 230 | + expect(schema1.options).toStrictEqual([{ value: 'c', label: 'C' }]) |
| 231 | + // preserves the reference identity of the incoming options array |
| 232 | + expect(schema1.options).toEqual(incomingOptions) |
| 233 | + expect(schema1.options).toHaveLength(1) |
| 234 | + }) |
| 235 | + |
| 236 | + it('should replace the whole option-like array rather than merging it by index', () => { |
| 237 | + const schema1: Record<string, any> = { |
| 238 | + anyOf: [{ const: 'A', label: 'A' }, { const: 'B', label: 'B' }], |
| 239 | + } |
| 240 | + const incomingAnyOf = [{ const: 'C', label: 'C' }] |
| 241 | + deepMergeSchemas(schema1, { |
| 242 | + anyOf: incomingAnyOf, |
| 243 | + }) |
| 244 | + // preserves the reference identity of the incoming anyOf array |
| 245 | + expect(schema1.anyOf).toEqual(incomingAnyOf) |
| 246 | + expect(schema1.anyOf).toHaveLength(1) |
| 247 | + }) |
| 248 | + |
| 249 | + it('should replace the whole option-like array, preserving null const values', () => { |
| 250 | + const schema1: Record<string, any> = { |
| 251 | + oneOf: [{ const: 'A', label: 'A' }], |
| 252 | + } |
| 253 | + const incomingOneOf = [{ const: 'C', label: 'C' }, { const: null, label: 'N/A' }] |
| 254 | + deepMergeSchemas(schema1, { |
| 255 | + oneOf: incomingOneOf, |
| 256 | + }) |
| 257 | + expect(schema1.oneOf).toStrictEqual([{ const: 'C', label: 'C' }, { const: null, label: 'N/A' }]) |
| 258 | + |
| 259 | + expect(schema1.oneOf).toHaveLength(2) |
| 260 | + }) |
| 261 | + |
| 262 | + it('should index-merge arrays of objects for non-option-like values ', () => { |
| 263 | + // Unlike `options`, other object arrays are merged recursively by index. |
| 264 | + const schema1: Record<string, any> = { |
| 265 | + anyOf: [{ type: 'string', title: 'Old' }, { type: 'number' }], |
| 266 | + } |
| 267 | + deepMergeSchemas(schema1, { |
| 268 | + anyOf: [{ title: 'New' }], |
| 269 | + }) |
| 270 | + expect(schema1.anyOf).toStrictEqual([ |
| 271 | + { type: 'string', title: 'New' }, |
| 272 | + { type: 'number' }, |
| 273 | + ]) |
| 274 | + }) |
| 275 | + |
| 276 | + it('should handle incoming null values for option-like arrays', () => { |
| 277 | + const schema1: Record<string, any> = { |
| 278 | + anyOf: [{ const: 'A', label: 'A' }, { const: 'B', label: 'B' }], |
| 279 | + } |
| 280 | + expect(() => deepMergeSchemas(schema1, { anyOf: [{ const: 'C', label: 'C' }, null] })).not.toThrow() |
| 281 | + expect(schema1.anyOf).toStrictEqual([{ const: 'C', label: 'C' }, null]) |
| 282 | + }) |
| 283 | + |
| 284 | + it('should replace the original array if it is option-like', () => { |
| 285 | + const schema1: Record<string, any> = { |
| 286 | + anyOf: [{ const: 'A', label: 'A' }, { const: 'B', label: 'B' }], |
| 287 | + } |
| 288 | + |
| 289 | + deepMergeSchemas(schema1, { anyOf: [] }) |
| 290 | + expect(schema1.anyOf).toStrictEqual([]) |
| 291 | + }) |
| 292 | +}) |
0 commit comments