Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 84 additions & 15 deletions packages/core/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
import {encode, decode, getCharCodesFromSource} from '../src';
import {
decode,
encode,
getCharCodesFromSource,
getCharsFrequency,
getEntropyOfText,
getRelativeFrequency,
getTree,
} from '../src';

const roundTrip = (text: string): string => {
const codes = getCharCodesFromSource(text);
const encoded = encode(text, codes);
return decode(encoded, codes);
};

const isPrefixFree = (codes: Map<string, string>): boolean => {
const values = Array.from(codes.values());
return values.every(
(code, index) =>
code === '' ||
values.every((other, otherIndex) => index === otherIndex || !other.startsWith(code)),
);
};

describe('Check Encode/decode', () => {
it('abra', () => {
Expand All @@ -9,13 +32,9 @@ describe('Check Encode/decode', () => {
expect(decoded).toEqual(text);
});

// it('aaaaaaaaaa', () => {
// const text = 'aaaaaaaaaa';
// const codes = getCodesFromText(text);
// const encoded = encode(text, codes);
// const decoded = decode(encoded, codes);
// expect(decoded).toEqual(text);
// });
it('round-trips a single repeated symbol', () => {
expect(roundTrip('aaaaaaaaaa')).toEqual('aaaaaaaaaa');
});

it('1234', () => {
const text = '1234123121';
Expand All @@ -25,11 +44,61 @@ describe('Check Encode/decode', () => {
expect(decoded).toEqual(text);
});

// it('', () => {
// const text = '';
// const codes = getCodesFromText(text);
// const encoded = encode(text, codes);
// const decoded = decode(encoded, codes);
// expect(decoded).toEqual(text);
// });
it('round-trips empty text', () => {
const text = '';
const codes = getCharCodesFromSource(text);
const encoded = encode(text, codes);
const decoded = decode(encoded, codes);
expect(encoded).toEqual([]);
expect(decoded).toEqual(text);
});

it('round-trips punctuation, whitespace, and unicode text', () => {
expect(roundTrip('hello, huffman!\nhello, compression!')).toEqual(
'hello, huffman!\nhello, compression!',
);
expect(roundTrip('banana bandana 123')).toEqual('banana bandana 123');
});
});

describe('Frequency helpers', () => {
it('counts character frequency in descending order', () => {
expect(getCharsFrequency('banana')).toEqual([
['a', 3],
['n', 2],
['b', 1],
]);
});

it('calculates relative frequencies from absolute counts', () => {
expect(getRelativeFrequency([
['a', 3],
['b', 1],
])).toEqual([
['a', 0.75],
['b', 0.25],
]);
});

it('calculates entropy for common simple distributions', () => {
expect(getEntropyOfText('aaaa')).toBe(-0);
expect(getEntropyOfText('abab')).toBe(1);
});
});

describe('Huffman tree and code generation', () => {
it('creates a root node weighted by the source length', () => {
const tree = getTree(getCharsFrequency('mississippi'));
expect(tree.weight).toBe('mississippi'.length);
});

it('creates one code per unique character', () => {
const codes = getCharCodesFromSource('mississippi');
expect(Array.from(codes.keys()).sort()).toEqual(['i', 'm', 'p', 's']);
});

it('generates prefix-free codes for multi-character input', () => {
const codes = getCharCodesFromSource('mississippi river');
expect(isPrefixFree(codes)).toBe(true);
});
});