|
| 1 | +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ |
1 | 2 | import { readConfObject } from '@jbrowse/core/configuration' |
2 | | -import { getSnapshot } from '@jbrowse/mobx-state-tree' |
3 | 3 |
|
4 | 4 | import sharedVariantConfigFactory from './SharedVariantConfigSchema.ts' |
| 5 | +import { applyColorPalette } from './applyColorPalette.ts' |
5 | 6 |
|
6 | 7 | describe('SharedVariantConfigSchema', () => { |
7 | 8 | const configSchema = sharedVariantConfigFactory() |
@@ -103,7 +104,6 @@ describe('SharedVariantConfigSchema', () => { |
103 | 104 | }) |
104 | 105 |
|
105 | 106 | describe('Config-to-getter fallback logic', () => { |
106 | | - |
107 | 107 | it('referenceDrawingMode getter returns draw when showReferenceAlleles config is true', () => { |
108 | 108 | const showReferenceAlleles = true |
109 | 109 | const referenceDrawingModeSetting: string | undefined = undefined |
@@ -200,3 +200,121 @@ describe('Config-to-getter fallback logic', () => { |
200 | 200 | expect(result).toBe(0.2) |
201 | 201 | }) |
202 | 202 | }) |
| 203 | + |
| 204 | +describe('colorBy config slot', () => { |
| 205 | + const configSchema = sharedVariantConfigFactory() |
| 206 | + |
| 207 | + it('has default value of empty string', () => { |
| 208 | + const config = configSchema.create({ |
| 209 | + type: 'SharedVariantDisplay', |
| 210 | + displayId: 'test-colorby-1', |
| 211 | + }) |
| 212 | + expect(readConfObject(config, 'colorBy')).toBe('') |
| 213 | + }) |
| 214 | + |
| 215 | + it('can be set to a metadata attribute name', () => { |
| 216 | + const config = configSchema.create({ |
| 217 | + type: 'SharedVariantDisplay', |
| 218 | + displayId: 'test-colorby-2', |
| 219 | + colorBy: 'population', |
| 220 | + }) |
| 221 | + expect(readConfObject(config, 'colorBy')).toBe('population') |
| 222 | + }) |
| 223 | +}) |
| 224 | + |
| 225 | +describe('applyColorPalette', () => { |
| 226 | + it('returns original sources when attribute is empty', () => { |
| 227 | + const sources = [ |
| 228 | + { name: 'sample1', population: 'EUR' }, |
| 229 | + { name: 'sample2', population: 'AFR' }, |
| 230 | + ] |
| 231 | + const result = applyColorPalette(sources, '') |
| 232 | + expect(result).toBe(sources) |
| 233 | + }) |
| 234 | + |
| 235 | + it('returns original sources when sources array is empty', () => { |
| 236 | + const sources: { name: string }[] = [] |
| 237 | + const result = applyColorPalette(sources, 'population') |
| 238 | + expect(result).toBe(sources) |
| 239 | + }) |
| 240 | + |
| 241 | + it('returns original sources when attribute does not exist', () => { |
| 242 | + const sources = [ |
| 243 | + { name: 'sample1', population: 'EUR' }, |
| 244 | + { name: 'sample2', population: 'AFR' }, |
| 245 | + ] |
| 246 | + const result = applyColorPalette(sources, 'nonexistent') |
| 247 | + expect(result).toBe(sources) |
| 248 | + }) |
| 249 | + |
| 250 | + it('applies colors based on attribute values', () => { |
| 251 | + const sources = [ |
| 252 | + { name: 'sample1', population: 'EUR' }, |
| 253 | + { name: 'sample2', population: 'AFR' }, |
| 254 | + { name: 'sample3', population: 'EUR' }, |
| 255 | + ] |
| 256 | + const result = applyColorPalette(sources, 'population') |
| 257 | + |
| 258 | + expect(result).toHaveLength(3) |
| 259 | + expect(result[0]).toHaveProperty('color') |
| 260 | + expect(result[1]).toHaveProperty('color') |
| 261 | + expect(result[2]).toHaveProperty('color') |
| 262 | + |
| 263 | + // Samples with same population value should have same color |
| 264 | + expect(result[0]!.color).toBe(result[2]!.color) |
| 265 | + // Samples with different population values should have different colors |
| 266 | + expect(result[0]!.color).not.toBe(result[1]!.color) |
| 267 | + }) |
| 268 | + |
| 269 | + it('assigns colors by frequency (less common values get colors first)', () => { |
| 270 | + const sources = [ |
| 271 | + { name: 'sample1', population: 'EUR' }, |
| 272 | + { name: 'sample2', population: 'EUR' }, |
| 273 | + { name: 'sample3', population: 'EUR' }, |
| 274 | + { name: 'sample4', population: 'AFR' }, |
| 275 | + ] |
| 276 | + const result = applyColorPalette(sources, 'population') |
| 277 | + |
| 278 | + // AFR (1 occurrence) should get the first color from palette |
| 279 | + // EUR (3 occurrences) should get the second color |
| 280 | + const afrSample = result.find(s => s.population === 'AFR') |
| 281 | + const eurSample = result.find(s => s.population === 'EUR') |
| 282 | + |
| 283 | + expect(afrSample).toHaveProperty('color') |
| 284 | + expect(eurSample).toHaveProperty('color') |
| 285 | + expect(afrSample!.color).not.toBe(eurSample!.color) |
| 286 | + }) |
| 287 | + |
| 288 | + it('preserves other properties on sources', () => { |
| 289 | + const sources = [ |
| 290 | + { name: 'sample1', population: 'EUR', region: 'Western', custom: 123 }, |
| 291 | + { name: 'sample2', population: 'AFR', region: 'Eastern', custom: 456 }, |
| 292 | + ] |
| 293 | + const result = applyColorPalette(sources, 'population') |
| 294 | + |
| 295 | + expect(result[0]!.name).toBe('sample1') |
| 296 | + // @ts-expect-error |
| 297 | + expect(result[0]!.region).toBe('Western') |
| 298 | + // @ts-expect-error |
| 299 | + expect(result[0]!.custom).toBe(123) |
| 300 | + expect(result[1]!.name).toBe('sample2') |
| 301 | + // @ts-expect-error |
| 302 | + expect(result[1]!.region).toBe('Eastern') |
| 303 | + // @ts-expect-error |
| 304 | + expect(result[1]!.custom).toBe(456) |
| 305 | + }) |
| 306 | + |
| 307 | + it('handles undefined attribute values by converting to string', () => { |
| 308 | + const sources = [ |
| 309 | + { name: 'sample1', population: 'EUR' }, |
| 310 | + { name: 'sample2' }, // no population attribute |
| 311 | + ] |
| 312 | + const result = applyColorPalette(sources, 'population') |
| 313 | + |
| 314 | + expect(result).toHaveLength(2) |
| 315 | + expect(result[0]).toHaveProperty('color') |
| 316 | + expect(result[1]).toHaveProperty('color') |
| 317 | + // They should have different colors (EUR vs undefined) |
| 318 | + expect(result[0]!.color).not.toBe(result[1]!.color) |
| 319 | + }) |
| 320 | +}) |
0 commit comments