waaraawa_ByteGrid/packages/core/tests/layoutEngine.test.ts
waaraawa 482b458370 feat(core): add automatic color assignment for fields
- Add autoColor option (default: true)
    - true: Auto-assign colors to fields without explicit colors
    - false: Use gray for fields without explicit colors
  - Auto color sequence: blue, cyan, yellow, green, orange, purple, mint, pink
  - Padding/reserved fields always use gray
  - Explicit colors always take precedence

  Examples & Documentation:
  - Add 4 example files (basic, manual, mixed, interactive guide)
  - Update examples/README.md with Auto Color section
  - Add Color Options explanation

  Tests:
  - Add 8 new tests (5 layoutEngine, 3 parser)
  - Update existing test for autoColor: false
2025-10-22 22:24:24 +09:00

508 lines
15 KiB
TypeScript

/**
* LayoutEngine module tests
* Tests for converting Fields to LayoutBlocks
*/
import { createLayout } from '../src/layoutEngine';
import { ByteGridConfig } from '../src/types';
describe('LayoutEngine', () => {
describe('createLayout()', () => {
it('should create layout blocks for single-row fields', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
layout: 16,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t', color: 'blue' },
{ offset: '4-7', name: 'Field2', type: 'uint32_t', color: 'cyan' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(2);
// Field1: bytes 0-3
expect(blocks[0].row).toBe(0);
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(4);
expect(blocks[0].fieldName).toBe('Field1');
expect(blocks[0].color).toBe('blue');
expect(blocks[0].offsetStart).toBe(0);
expect(blocks[0].offsetEnd).toBe(3);
// Field2: bytes 4-7
expect(blocks[1].row).toBe(0);
expect(blocks[1].col).toBe(4);
expect(blocks[1].span).toBe(4);
expect(blocks[1].fieldName).toBe('Field2');
expect(blocks[1].color).toBe('cyan');
});
it('should split field across multiple rows', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 32,
layout: 8,
fields: [
{ offset: '0-15', name: 'LargeField', type: 'uint8_t[16]', color: 'green' },
],
};
const blocks = createLayout(config);
// Should split into 2 blocks (8 bytes each)
expect(blocks).toHaveLength(2);
// First row: bytes 0-7
expect(blocks[0].row).toBe(0);
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(8);
expect(blocks[0].fieldName).toBe('LargeField');
expect(blocks[0].offsetStart).toBe(0);
expect(blocks[0].offsetEnd).toBe(7);
// Second row: bytes 8-15
expect(blocks[1].row).toBe(1);
expect(blocks[1].col).toBe(0);
expect(blocks[1].span).toBe(8);
expect(blocks[1].fieldName).toBe('LargeField');
expect(blocks[1].offsetStart).toBe(8);
expect(blocks[1].offsetEnd).toBe(15);
});
it('should handle field starting mid-row', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
layout: 8,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t' },
{ offset: '4-11', name: 'Field2', type: 'uint64_t', color: 'purple' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(3);
// Field1: bytes 0-3 (row 0, col 0)
expect(blocks[0].row).toBe(0);
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(4);
// Field2 part 1: bytes 4-7 (row 0, col 4)
expect(blocks[1].row).toBe(0);
expect(blocks[1].col).toBe(4);
expect(blocks[1].span).toBe(4);
expect(blocks[1].fieldName).toBe('Field2');
expect(blocks[1].offsetStart).toBe(4);
expect(blocks[1].offsetEnd).toBe(7);
// Field2 part 2: bytes 8-11 (row 1, col 0)
expect(blocks[2].row).toBe(1);
expect(blocks[2].col).toBe(0);
expect(blocks[2].span).toBe(4);
expect(blocks[2].fieldName).toBe('Field2');
expect(blocks[2].offsetStart).toBe(8);
expect(blocks[2].offsetEnd).toBe(11);
});
it('should default to gray color for fields without color when autoColor is false', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 8,
autoColor: false,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t' },
],
};
const blocks = createLayout(config);
expect(blocks[0].color).toBe('gray');
});
it('should mark reserved and padding types', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
fields: [
{ offset: '0-3', name: 'Data', type: 'uint32_t' },
{ offset: '4-7', name: 'Reserved', type: 'reserved' },
{ offset: '8-11', name: 'Padding', type: 'padding' },
],
};
const blocks = createLayout(config);
expect(blocks[0].isPadding).toBe(false);
expect(blocks[1].isPadding).toBe(true);
expect(blocks[2].isPadding).toBe(true);
});
it('should preserve field metadata', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
fields: [
{
offset: '0-3',
name: 'ChunkID',
type: 'char[4]',
value: 'RIFF',
description: 'RIFF magic number',
color: 'blue',
},
],
};
const blocks = createLayout(config);
expect(blocks[0].value).toBe('RIFF');
expect(blocks[0].description).toBe('RIFF magic number');
});
it('should handle single-byte fields', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 8,
fields: [
{ offset: '0', name: 'Byte0', type: 'uint8_t' },
{ offset: '1', name: 'Byte1', type: 'uint8_t' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(2);
expect(blocks[0].span).toBe(1);
expect(blocks[1].span).toBe(1);
});
it('should handle bitfields', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 8,
fields: [
{
offset: '0',
name: 'Flags',
type: 'uint8_t',
bitfields: [
{ name: 'Bit7', bits: '7' },
{ name: 'Bit6', bits: '6' },
],
},
],
};
const blocks = createLayout(config);
expect(blocks[0].bitfields).toBeDefined();
expect(blocks[0].bitfields).toHaveLength(2);
});
it('should work with layout=16 (default)', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 32,
fields: [
{ offset: '0-15', name: 'Row1', type: 'uint8_t[16]' },
{ offset: '16-31', name: 'Row2', type: 'uint8_t[16]' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(2);
expect(blocks[0].row).toBe(0);
expect(blocks[1].row).toBe(1);
});
it('should handle complex field arrangement', () => {
const config: ByteGridConfig = {
name: 'WAV Header',
size: 44,
layout: 16,
fields: [
{ offset: '0-3', name: 'ChunkID', type: 'char[4]', color: 'blue' },
{ offset: '4-7', name: 'ChunkSize', type: 'uint32_t', color: 'cyan' },
{ offset: '8-11', name: 'Format', type: 'char[4]', color: 'blue' },
{ offset: '12-15', name: 'Subchunk1ID', type: 'char[4]', color: 'orange' },
],
};
const blocks = createLayout(config);
// All in first row
expect(blocks).toHaveLength(4);
blocks.forEach((block) => {
expect(block.row).toBe(0);
});
expect(blocks[0].col).toBe(0);
expect(blocks[1].col).toBe(4);
expect(blocks[2].col).toBe(8);
expect(blocks[3].col).toBe(12);
});
});
describe('createLayout() - bit-unit layout', () => {
it('should create layout blocks for bit-unit fields', () => {
const config: ByteGridConfig = {
name: 'Bit Layout',
size: 16, // 16 bits
layoutUnit: 'bit',
layout: 16, // 16 bits per row
fields: [
{ offset: '0-7b', name: 'FirstByte', type: 'uint8_t', color: 'blue' },
{ offset: '8-15b', name: 'SecondByte', type: 'uint8_t', color: 'cyan' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(2);
// FirstByte: bits 0-7
expect(blocks[0].row).toBe(0);
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(8);
expect(blocks[0].fieldName).toBe('FirstByte');
expect(blocks[0].offsetStart).toBe(0);
expect(blocks[0].offsetEnd).toBe(7);
// SecondByte: bits 8-15
expect(blocks[1].row).toBe(0);
expect(blocks[1].col).toBe(8);
expect(blocks[1].span).toBe(8);
expect(blocks[1].fieldName).toBe('SecondByte');
});
it('should handle Byte offset in bit layout', () => {
const config: ByteGridConfig = {
name: 'Mixed',
size: 16, // 16 bits
layoutUnit: 'bit',
layout: 16,
fields: [
{ offset: '0B', name: 'ByteField', type: 'uint8_t', color: 'blue' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(1);
// 0B = byte 0 = bits 0-7
expect(blocks[0].row).toBe(0);
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(8);
expect(blocks[0].offsetStart).toBe(0);
expect(blocks[0].offsetEnd).toBe(7);
});
it('should split bit fields across multiple rows', () => {
const config: ByteGridConfig = {
name: 'Multi-row Bits',
size: 32, // 32 bits
layoutUnit: 'bit',
layout: 16, // 16 bits per row
fields: [
{ offset: '0-23b', name: 'LargeBitField', type: 'bits', color: 'green' },
],
};
const blocks = createLayout(config);
// Should split into 2 blocks (16 bits each)
expect(blocks).toHaveLength(2);
// First row: bits 0-15
expect(blocks[0].row).toBe(0);
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(16);
expect(blocks[0].offsetStart).toBe(0);
expect(blocks[0].offsetEnd).toBe(15);
// Second row: bits 16-23
expect(blocks[1].row).toBe(1);
expect(blocks[1].col).toBe(0);
expect(blocks[1].span).toBe(8);
expect(blocks[1].offsetStart).toBe(16);
expect(blocks[1].offsetEnd).toBe(23);
});
it('should handle mixed bit and Byte offsets', () => {
const config: ByteGridConfig = {
name: 'IPv4 Header',
size: 32, // 32 bits
layoutUnit: 'bit',
layout: 32,
fields: [
{ offset: '0-3b', name: 'Version', type: 'bits', color: 'blue' },
{ offset: '4-7b', name: 'IHL', type: 'bits', color: 'cyan' },
{ offset: '1B', name: 'DSCP', type: 'uint8_t', color: 'yellow' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(3);
// Version: bits 0-3
expect(blocks[0].col).toBe(0);
expect(blocks[0].span).toBe(4);
// IHL: bits 4-7
expect(blocks[1].col).toBe(4);
expect(blocks[1].span).toBe(4);
// DSCP: byte 1 = bits 8-15
expect(blocks[2].col).toBe(8);
expect(blocks[2].span).toBe(8);
expect(blocks[2].offsetStart).toBe(8);
expect(blocks[2].offsetEnd).toBe(15);
});
it('should handle 4-bit fields', () => {
const config: ByteGridConfig = {
name: '4-bit Fields',
size: 8, // 8 bits = 1 byte
layoutUnit: 'bit',
layout: 8,
fields: [
{ offset: '0-3b', name: 'Nibble1', type: 'bits' },
{ offset: '4-7b', name: 'Nibble2', type: 'bits' },
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(2);
expect(blocks[0].span).toBe(4);
expect(blocks[1].span).toBe(4);
});
});
describe('autoColor feature', () => {
it('should auto-assign colors when autoColor is true (default)', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
layout: 16,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t' }, // No color specified
{ offset: '4-7', name: 'Field2', type: 'uint32_t' }, // No color specified
{ offset: '8-11', name: 'Field3', type: 'uint32_t' }, // No color specified
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(3);
// Should auto-assign: blue, cyan, yellow
expect(blocks[0].color).toBe('blue');
expect(blocks[1].color).toBe('cyan');
expect(blocks[2].color).toBe('yellow');
});
it('should use gray when autoColor is false and no color specified', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
layout: 16,
autoColor: false,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t' }, // No color specified
{ offset: '4-7', name: 'Field2', type: 'uint32_t' }, // No color specified
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(2);
// Should default to gray
expect(blocks[0].color).toBe('gray');
expect(blocks[1].color).toBe('gray');
});
it('should respect explicit colors even when autoColor is true', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
layout: 16,
autoColor: true,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t', color: 'pink' }, // Explicit color
{ offset: '4-7', name: 'Field2', type: 'uint32_t' }, // Auto-assign
{ offset: '8-11', name: 'Field3', type: 'uint32_t', color: 'mint' }, // Explicit color
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(3);
expect(blocks[0].color).toBe('pink'); // Explicit
expect(blocks[1].color).toBe('cyan'); // Auto (fieldIndex=1 -> cyan)
expect(blocks[2].color).toBe('mint'); // Explicit
});
it('should use gray for padding/reserved fields regardless of autoColor', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 16,
layout: 16,
autoColor: true,
fields: [
{ offset: '0-3', name: 'Field1', type: 'uint32_t' }, // Auto-assign
{ offset: '4-7', name: 'Reserved', type: 'reserved' }, // Should be gray
{ offset: '8-11', name: 'Field2', type: 'uint32_t' }, // Auto-assign
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(3);
expect(blocks[0].color).toBe('blue'); // Auto
expect(blocks[1].color).toBe('gray'); // Reserved
expect(blocks[2].color).toBe('yellow'); // Auto (fieldIndex=2 -> yellow)
});
it('should cycle through colors for many fields', () => {
const config: ByteGridConfig = {
name: 'Test',
size: 36,
layout: 16,
autoColor: true,
fields: [
{ offset: '0-3', name: 'F1', type: 'uint32_t' },
{ offset: '4-7', name: 'F2', type: 'uint32_t' },
{ offset: '8-11', name: 'F3', type: 'uint32_t' },
{ offset: '12-15', name: 'F4', type: 'uint32_t' },
{ offset: '16-19', name: 'F5', type: 'uint32_t' },
{ offset: '20-23', name: 'F6', type: 'uint32_t' },
{ offset: '24-27', name: 'F7', type: 'uint32_t' },
{ offset: '28-31', name: 'F8', type: 'uint32_t' },
{ offset: '32-35', name: 'F9', type: 'uint32_t' }, // Should cycle back to blue
],
};
const blocks = createLayout(config);
expect(blocks).toHaveLength(9);
// First 8 colors
expect(blocks[0].color).toBe('blue');
expect(blocks[1].color).toBe('cyan');
expect(blocks[2].color).toBe('yellow');
expect(blocks[3].color).toBe('green');
expect(blocks[4].color).toBe('orange');
expect(blocks[5].color).toBe('purple');
expect(blocks[6].color).toBe('mint');
expect(blocks[7].color).toBe('pink');
// Cycle back
expect(blocks[8].color).toBe('blue');
});
});
});