import { describe, it, expect } from 'vitest' import { convertWavelength, interpolateData, parseDIF, extractMetadata, } from './xrd-processing' // --------------------------------------------------------------------------- // convertWavelength // --------------------------------------------------------------------------- describe('convertWavelength', () => { it('returns same angle when wavelengths match', () => { const result = convertWavelength(10.0, 0.6199, 0.6199) expect(result).toBeCloseTo(10.0, 5) }) it('returns null for physically impossible conversion', () => { // Large angle with large wavelength ratio can exceed sin > 1 const result = convertWavelength(80.0, 0.5, 2.0) expect(result).toBeNull() }) it('converts Cu Ka to synchrotron wavelength', () => { const cuKa = 1.5406 const synchrotron = 0.6199 const theta = 20.0 const result = convertWavelength(theta, cuKa, synchrotron) expect(result).not.toBeNull() // Shorter wavelength -> smaller 2theta expect(result).toBeLessThan(theta) expect(result).toBeGreaterThan(0) }) it('converts synchrotron to Cu Ka wavelength', () => { const cuKa = 1.5406 const synchrotron = 0.6199 const theta = 10.0 const result = convertWavelength(theta, synchrotron, cuKa) expect(result).not.toBeNull() // Longer wavelength -> larger 2theta expect(result).toBeGreaterThan(theta) }) it('handles zero angle', () => { const result = convertWavelength(0, 1.0, 2.0) expect(result).toBeCloseTo(0, 5) }) }) // --------------------------------------------------------------------------- // interpolateData // --------------------------------------------------------------------------- describe('interpolateData', () => { it('returns same data when target size matches', () => { const x = [1, 2, 3] const y = [10, 20, 30] const result = interpolateData(x, y, 3) expect(result.x).toEqual(x) expect(result.y).toEqual(y) }) it('interpolates to larger size with linear strategy', () => { const x = [0, 10] const y = [0, 100] const result = interpolateData(x, y, 11, 0, 10, 'linear') expect(result.x).toHaveLength(11) expect(result.y).toHaveLength(11) // Midpoint should be ~50 expect(result.y[5]).toBeCloseTo(50, 0) }) it('sets out-of-range values to zero', () => { const x = [5, 10, 15] const y = [100, 200, 300] const result = interpolateData(x, y, 20, 0, 20, 'linear') // Points before x=5 should be 0 expect(result.y[0]).toBe(0) // Points after x=15 should be 0 expect(result.y[19]).toBe(0) }) it('supports cubic interpolation', () => { const x = [0, 2, 4, 6, 8, 10] const y = [0, 4, 16, 36, 64, 100] const result = interpolateData(x, y, 11, 0, 10, 'cubic') expect(result.x).toHaveLength(11) expect(result.y).toHaveLength(11) }) it('handles single point input', () => { const x = [5] const y = [100] const result = interpolateData(x, y, 10, 0, 10, 'linear') expect(result.x).toHaveLength(10) }) }) // --------------------------------------------------------------------------- // parseDIF // --------------------------------------------------------------------------- describe('parseDIF', () => { it('parses space-separated data', () => { const text = '5.0 100.0\n10.0 200.0\n15.0 300.0\n' const result = parseDIF(text) expect(result.x).toEqual([5.0, 10.0, 15.0]) expect(result.y).toEqual([100.0, 200.0, 300.0]) }) it('skips comment lines', () => { const text = '# This is a comment\n5.0 100.0\n10.0 200.0\n' const result = parseDIF(text) expect(result.x).toHaveLength(2) }) it('skips metadata lines', () => { const text = 'CELL PARAMETERS: 5.0 5.0 5.0 90 90 90\nSPACE GROUP: Fm-3m\n5.0 100.0\n' const result = parseDIF(text) expect(result.x).toEqual([5.0]) }) it('skips lines starting with underscore', () => { const text = '_cell_length_a 5.431\n5.0 100.0\n' const result = parseDIF(text) expect(result.x).toEqual([5.0]) }) it('handles empty input', () => { const result = parseDIF('') expect(result.x).toEqual([]) expect(result.y).toEqual([]) }) it('handles tab-separated data', () => { const text = '5.0\t100.0\n10.0\t200.0\n' const result = parseDIF(text) expect(result.x).toEqual([5.0, 10.0]) expect(result.y).toEqual([100.0, 200.0]) }) it('skips lines starting with letters', () => { const text = 'Header line\n5.0 100.0\nAnother header\n10.0 200.0\n' const result = parseDIF(text) expect(result.x).toEqual([5.0, 10.0]) }) }) // --------------------------------------------------------------------------- // extractMetadata // --------------------------------------------------------------------------- describe('extractMetadata', () => { it('detects wavelength from numeric pattern', () => { const text = '_diffrn_radiation_wavelength 0.6199\n' const result = extractMetadata(text) expect(result.wavelength).toBeCloseTo(0.6199, 4) }) it('detects Cu Ka radiation', () => { const text = 'Radiation: Cu Ka\n' const result = extractMetadata(text) expect(result.wavelength).toBeCloseTo(1.5406, 4) }) it('detects Mo Ka radiation', () => { const text = 'Radiation: Mo Ka\n' const result = extractMetadata(text) expect(result.wavelength).toBeCloseTo(0.7107, 4) }) it('extracts space group number', () => { const text = '_symmetry_Int_Tables_number 225\n' const result = extractMetadata(text) expect(result.spaceGroup).toBe('225') }) it('extracts cell parameters', () => { const text = 'CELL PARAMETERS: 5.431 5.431 5.431 90.0 90.0 90.0\n' const result = extractMetadata(text) expect(result.cellParams).not.toBeNull() expect(result.cellParams).toContain('5.431') }) it('returns null for missing data', () => { const result = extractMetadata('Some random text without metadata\n') expect(result.wavelength).toBeNull() expect(result.spaceGroup).toBeNull() expect(result.cellParams).toBeNull() }) it('rejects wavelengths outside X-ray range', () => { const text = 'wavelength: 0.001\n' const result = extractMetadata(text) expect(result.wavelength).toBeNull() }) })